[libosmium] 01/05: Imported Upstream version 2.3.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Tue Aug 18 13:46:47 UTC 2015


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

sebastic pushed a commit to branch master
in repository libosmium.

commit 07ba40b71e398f15b6024a0c5b0e5c22b520066b
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Aug 18 14:08:44 2015 +0200

    Imported Upstream version 2.3.0
---
 .travis.yml                                        |   10 +-
 CHANGELOG.md                                       |   44 +-
 CMakeLists.txt                                     |    2 +-
 EXTERNAL_LICENSES.txt                              |  233 ++++
 README.md                                          |    4 +
 appveyor.yml                                       |   12 +-
 benchmarks/CMakeLists.txt                          |    1 +
 benchmarks/osmium_benchmark_count.cpp              |    2 -
 benchmarks/osmium_benchmark_count_tag.cpp          |    2 -
 benchmarks/osmium_benchmark_index_map.cpp          |    2 -
 .../osmium_benchmark_static_vs_dynamic_index.cpp   |    1 -
 benchmarks/osmium_benchmark_write_pbf.cpp          |   34 +
 benchmarks/run_benchmark_write_pbf.sh              |   28 +
 cmake/FindOSMPBF.cmake                             |   50 -
 cmake/FindOsmium.cmake                             |    8 +-
 cmake/iwyu.sh                                      |    4 +-
 examples/osmium_area_test.cpp                      |    2 -
 examples/osmium_convert.cpp                        |    1 -
 examples/osmium_count.cpp                          |    2 -
 examples/osmium_create_node_cache.cpp              |    2 -
 examples/osmium_debug.cpp                          |    2 -
 examples/osmium_read.cpp                           |    2 -
 examples/osmium_serdump.cpp                        |    2 -
 examples/osmium_toogr.cpp                          |    2 -
 examples/osmium_toogr2.cpp                         |    2 -
 examples/osmium_toogr2_exp.cpp                     |    2 -
 examples/osmium_use_node_cache.cpp                 |    2 -
 include/boost_unicode_iterator.hpp                 |  776 -----------
 include/osmium/area/multipolygon_collector.hpp     |    1 +
 include/osmium/builder/builder.hpp                 |   24 +-
 include/osmium/builder/osm_object_builder.hpp      |   53 +-
 include/osmium/geom/geos.hpp                       |   35 +-
 include/osmium/geom/wkb.hpp                        |   10 +-
 include/osmium/index/detail/create_map_with_fd.hpp |    2 -
 include/osmium/index/detail/mmap_vector_base.hpp   |    4 +-
 include/osmium/index/detail/mmap_vector_file.hpp   |    3 +-
 include/osmium/index/map/dense_mmap_array.hpp      |    2 +-
 include/osmium/index/map/sparse_mem_map.hpp        |    2 +-
 include/osmium/io/any_input.hpp                    |    4 +-
 include/osmium/io/any_output.hpp                   |    5 +-
 include/osmium/io/bzip2_compression.hpp            |    5 +
 include/osmium/io/compression.hpp                  |    5 +
 .../osmium/io/{pbf_output.hpp => debug_output.hpp} |   18 +-
 include/osmium/io/detail/debug_output_format.hpp   |  482 +++++++
 include/osmium/io/detail/opl_output_format.hpp     |   75 +-
 include/osmium/io/detail/pbf.hpp                   |   23 +-
 include/osmium/io/detail/pbf_decoder.hpp           |  760 +++++++++++
 include/osmium/io/detail/pbf_input_format.hpp      |   94 +-
 include/osmium/io/detail/pbf_output_format.hpp     | 1055 +++++----------
 include/osmium/io/detail/pbf_parser.hpp            |  456 -------
 include/osmium/io/detail/pbf_stringtable.hpp       |  218 ----
 include/osmium/io/detail/pbf_type_conv.hpp         |   73 --
 include/osmium/io/detail/protobuf_tags.hpp         |  170 +++
 include/osmium/io/detail/string_table.hpp          |  250 ++++
 include/osmium/io/detail/xml_input_format.hpp      |   36 +-
 include/osmium/io/detail/xml_output_format.hpp     |   60 +-
 include/osmium/io/detail/zlib.hpp                  |   15 +-
 include/osmium/io/file.hpp                         |   68 +-
 include/osmium/io/file_format.hpp                  |    8 +-
 include/osmium/io/gzip_compression.hpp             |    5 +
 include/osmium/io/pbf_input.hpp                    |    1 -
 include/osmium/io/pbf_output.hpp                   |    1 -
 include/osmium/memory/buffer.hpp                   |    3 +-
 include/osmium/memory/collection.hpp               |    1 -
 include/osmium/memory/item.hpp                     |    1 -
 include/osmium/osm/changeset.hpp                   |    1 -
 include/osmium/osm/crc.hpp                         |  223 ++++
 include/osmium/osm/entity.hpp                      |    1 +
 include/osmium/osm/node_ref.hpp                    |    2 +-
 include/osmium/osm/timestamp.hpp                   |    3 +-
 include/osmium/thread/queue.hpp                    |    4 +-
 include/osmium/thread/util.hpp                     |    2 +-
 include/osmium/util/data_file.hpp                  |    2 +
 include/osmium/util/delta.hpp                      |   54 +-
 .../osmium/{io/pbf_input.hpp => util/endian.hpp}   |   26 +-
 include/osmium/util/file.hpp                       |    1 +
 include/osmium/util/memory_mapping.hpp             |  113 +-
 include/osmium/util/minmax.hpp                     |    3 -
 include/protozero/byteswap.hpp                     |   49 +
 include/protozero/exception.hpp                    |   68 +
 include/protozero/pbf_builder.hpp                  |  111 ++
 include/protozero/pbf_message.hpp                  |   50 +
 include/protozero/pbf_reader.hpp                   | 1059 +++++++++++++++
 include/protozero/pbf_types.hpp                    |   49 +
 include/protozero/pbf_writer.hpp                   |  664 ++++++++++
 include/protozero/varint.hpp                       |  136 ++
 include/{osmium/io/pbf_input.hpp => utf8.h}        |   29 +-
 include/utf8/checked.h                             |  327 +++++
 include/utf8/core.h                                |  329 +++++
 include/utf8/unchecked.h                           |  228 ++++
 scripts/travis_install.sh                          |   12 +-
 scripts/travis_script.sh                           |    2 -
 test/CMakeLists.txt                                |    7 +
 test/data-tests/testdata-testcases.cpp             |    2 -
 test/data-tests/testdata-xml.cpp                   |  115 +-
 test/include/catch.hpp                             | 1355 +++++++++++++-------
 test/include/catch_orig.hpp                        | 1347 ++++++++++++-------
 test/t/basic/helper.hpp                            |   32 +-
 test/t/basic/test_box.cpp                          |    7 +
 test/t/basic/test_changeset.cpp                    |   12 +-
 test/t/basic/test_crc.cpp                          |   49 +
 test/t/basic/test_node.cpp                         |    8 +
 test/t/basic/test_relation.cpp                     |   11 +-
 test/t/basic/test_way.cpp                          |   10 +-
 test/t/geom/test_geos.cpp                          |   35 +-
 test/t/io/test_file_formats.cpp                    |   24 +
 test/t/io/test_string_table.cpp                    |   94 ++
 test/t/tags/test_tag_list.cpp                      |  126 +-
 test/t/util/test_delta.cpp                         |   24 +
 test/t/util/test_memory_mapping.cpp                |   72 +-
 110 files changed, 8429 insertions(+), 3711 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 5499433..6ebdd71 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@
 
 language: cpp
 
-sudo: required
+sudo: false
 
 matrix:
     include:
@@ -33,17 +33,17 @@ matrix:
 addons:
     apt:
         sources:
+            - boost-latest
             - ubuntu-toolchain-r-test
         packages:
             - g++-4.8
             - gcc-4.8
-            - libboost-dev
-            - libboost-program-options-dev
+            - libboost1.55-dev
+            - libboost-program-options1.55-dev
+            - libgdal-dev
             - libgeos++-dev
             - libproj-dev
-            - libprotobuf-dev
             - libsparsehash-dev
-            - protobuf-compiler
             - spatialite-bin
 
 install:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e7bea6..22eb06a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,47 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Fixed
 
+## [2.3.0] - 2015-08-18
+
+### Added
+
+- Allow instantiating osmium::geom::GEOSFactory with existing GEOS factory.
+- Low-level functions to support generating a architecture- and endian-
+  independant CRC from OSM data. This is intended to be uses with boost::crc.
+- Add new debug output format. This format is not intended to be read
+  automatically, but for human consumption. It formats the data nicely.
+- Make writing of metadata configurable for XML and OPL output (use
+  `add_metadata=false` as file option).
+
+### Changed
+
+- Changed `add_user()` and `add_role()` in builders to use string length
+  without the 0-termination.
+- Improved code setting file format from suffix/format argument.
+- Memory mapping utility class now supports readonly, private writable or
+  shared writable operation.
+- Allow empty version (0) in PBF files.
+- Use utf8cpp header-only lib instead of boost for utf8 decoding. The library
+  is included in the libosmium distribution.
+- New PBF reader and writer based on the protozero. A complete rewrite of the
+  code for reading and writing OSM PBF files. It doesn't use the Google
+  protobuf library and it doesn't use the OSMPBF/OSM-Binary library any more.
+  Instead is uses the protozero lightweight protobuf header library which is
+  included in the code. Not only does the new code have less dependencies, it
+  is faster and more robust. https://github.com/mapbox/protozero
+
+### Fixed
+
+- Various smaller bug fixes.
+- Add encoding for relation member roles in OPL format.
+- Change character encoding to new format in OPL: variable length hex code
+  between % characters instead of a % followed by 4-digit hex code. This is
+  necessary because unicode characters can be longer than the 4-digit hex
+  code.
+- XML writer: The linefeed, carriage return, and tab characters are now
+  escaped properly.
+- Reading large XML files could block.
+
 ## [2.2.0] - 2015-07-04
 
 ### Added
@@ -67,7 +108,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   Doxygen (up to version 1.8.8). This version contains a workaround to fix
   this.
 
-[unreleased]: https://github.com/osmcode/libosmium/compare/v2.2.0...HEAD
+[unreleased]: https://github.com/osmcode/libosmium/compare/v2.3.0...HEAD
+[2.3.0]: https://github.com/osmcode/libosmium/compare/v2.3.0...v2.3.0
 [2.2.0]: https://github.com/osmcode/libosmium/compare/v2.1.0...v2.2.0
 [2.1.0]: https://github.com/osmcode/libosmium/compare/v2.0.0...v2.1.0
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f01a20b..fba967a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,7 +26,7 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev;Cover
 project(libosmium)
 
 set(LIBOSMIUM_VERSION_MAJOR 2)
-set(LIBOSMIUM_VERSION_MINOR 2)
+set(LIBOSMIUM_VERSION_MINOR 3)
 set(LIBOSMIUM_VERSION_PATCH 0)
 
 set(LIBOSMIUM_VERSION
diff --git a/EXTERNAL_LICENSES.txt b/EXTERNAL_LICENSES.txt
new file mode 100644
index 0000000..7b06fcf
--- /dev/null
+++ b/EXTERNAL_LICENSES.txt
@@ -0,0 +1,233 @@
+
+==== For protozero from https://github.com/mapbox/protozero
+
+protozero copyright (c) Mapbox.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in
+      the documentation and/or other materials provided with the
+      distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+==== For protozero from https://github.com/mapbox/protozero
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+==== For utf8.h
+
+Copyright 2006 Nemanja Trifunovic
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
diff --git a/README.md b/README.md
index 503440e..9676d80 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,10 @@ you need for your programs.
 For details see the
 [list of dependencies](https://github.com/osmcode/libosmium/wiki/Libosmium-dependencies).
 
+The [protozero](https://github.com/mapbox/protozero) and
+[utf8-cpp](http://utfcpp.sourceforge.net/) header-only libraries are included
+in the libosmium repository.
+
 
 ## Directories
 
diff --git a/appveyor.yml b/appveyor.yml
index 965603b..a05c396 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -29,7 +29,7 @@ clone_folder: c:\projects\libosmium
 platform: x64
 
 install:
-  # show all availble env vars
+  # show all available env vars
   - set
   - echo cmake on AppVeyor
   - cmake -version
@@ -77,11 +77,6 @@ build_script:
     -DBUILD_HEADERS=OFF
     -DBOOST_ROOT=%LODEPSDIR%\boost
     -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_57.lib
-    -DOSMPBF_LIBRARY=%LODEPSDIR%\osmpbf\lib\osmpbf.lib
-    -DOSMPBF_INCLUDE_DIR=%LODEPSDIR%\osmpbf\include
-    -DPROTOBUF_LIBRARY=%LODEPSDIR%\protobuf\lib\libprotobuf.lib
-    -DPROTOBUF_LITE_LIBRARY=%LODEPSDIR%\protobuf\lib\libprotobuf-lite.lib
-    -DPROTOBUF_INCLUDE_DIR=%LODEPSDIR%\protobuf\include
     -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib
     -DZLIB_INCLUDE_DIR=%LODEPSDIR%\zlib\include
     -DEXPAT_LIBRARY=%LODEPSDIR%\expat\lib\libexpat.lib
@@ -103,11 +98,6 @@ build_script:
   #  -DCMAKE_BUILD_TYPE=%config%
   #  -DBOOST_ROOT=%LODEPSDIR%\boost
   #  -DBoost_PROGRAM_OPTIONS_LIBRARY=%LODEPSDIR%\boost\lib\libboost_program_options-vc140-mt-1_57.lib
-  #  -DOSMPBF_LIBRARY=%LODEPSDIR%\osmpbf\lib\osmpbf.lib
-  #  -DOSMPBF_INCLUDE_DIR=%LODEPSDIR%\osmpbf\include
-  #  -DPROTOBUF_LIBRARY=%LODEPSDIR%\protobuf\lib\libprotobuf.lib
-  #  -DPROTOBUF_LITE_LIBRARY=%LODEPSDIR%\protobuf\lib\libprotobuf-lite.lib
-  #  -DPROTOBUF_INCLUDE_DIR=%LODEPSDIR%\protobuf\include
   #  -DZLIB_LIBRARY=%LODEPSDIR%\zlib\lib\zlibwapi.lib
   #  -DZLIB_INCLUDE_DIR=%LODEPSDIR%\zlib\include
   #  -DEXPAT_LIBRARY=%LODEPSDIR%\expat\lib\libexpat.lib
diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
index 6a4ca16..e46c833 100644
--- a/benchmarks/CMakeLists.txt
+++ b/benchmarks/CMakeLists.txt
@@ -13,6 +13,7 @@ set(BENCHMARKS
     count_tag
     index_map
     static_vs_dynamic_index
+    write_pbf
     CACHE STRING "Benchmark programs"
 )
 
diff --git a/benchmarks/osmium_benchmark_count.cpp b/benchmarks/osmium_benchmark_count.cpp
index 4409448..d50c53d 100644
--- a/benchmarks/osmium_benchmark_count.cpp
+++ b/benchmarks/osmium_benchmark_count.cpp
@@ -49,7 +49,5 @@ int main(int argc, char* argv[]) {
     std::cout << "Nodes: "     << handler.nodes << "\n";
     std::cout << "Ways: "      << handler.ways << "\n";
     std::cout << "Relations: " << handler.relations << "\n";
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/benchmarks/osmium_benchmark_count_tag.cpp b/benchmarks/osmium_benchmark_count_tag.cpp
index ca6771c..8fa696a 100644
--- a/benchmarks/osmium_benchmark_count_tag.cpp
+++ b/benchmarks/osmium_benchmark_count_tag.cpp
@@ -50,7 +50,5 @@ int main(int argc, char* argv[]) {
     reader.close();
 
     std::cout << "r_all=" << handler.all << " r_counter="  << handler.counter << "\n";
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/benchmarks/osmium_benchmark_index_map.cpp b/benchmarks/osmium_benchmark_index_map.cpp
index fa75fb2..0257826 100644
--- a/benchmarks/osmium_benchmark_index_map.cpp
+++ b/benchmarks/osmium_benchmark_index_map.cpp
@@ -35,7 +35,5 @@ int main(int argc, char* argv[]) {
 
     osmium::apply(reader, location_handler);
     reader.close();
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
index 9c47c84..66e2a0b 100644
--- a/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
+++ b/benchmarks/osmium_benchmark_static_vs_dynamic_index.cpp
@@ -46,7 +46,6 @@ int main(int argc, char* argv[]) {
     std::string input_filename = argv[1];
 
     osmium::memory::Buffer buffer = osmium::io::read_file(input_filename);
-    google::protobuf::ShutdownProtobufLibrary();
 
     const auto& map_factory = osmium::index::MapFactory<osmium::unsigned_object_id_type, osmium::Location>::instance();
 
diff --git a/benchmarks/osmium_benchmark_write_pbf.cpp b/benchmarks/osmium_benchmark_write_pbf.cpp
new file mode 100644
index 0000000..869f3a8
--- /dev/null
+++ b/benchmarks/osmium_benchmark_write_pbf.cpp
@@ -0,0 +1,34 @@
+/*
+
+  The code in this file is released into the Public Domain.
+
+*/
+
+#include <cstdint>
+#include <vector>
+
+#include <osmium/io/any_input.hpp>
+#include <osmium/io/any_output.hpp>
+
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        std::cerr << "Usage: " << argv[0] << " INPUT-FILE OUTPUT-FILE\n";
+        exit(1);
+    }
+
+    std::string input_filename = argv[1];
+    std::string output_filename = argv[2];
+
+    osmium::io::Reader reader(input_filename);
+    osmium::io::File output_file(output_filename, "pbf");
+    osmium::io::Header header;
+    osmium::io::Writer writer(output_file, header, osmium::io::overwrite::allow);
+
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        writer(std::move(buffer));
+    }
+
+    writer.close();
+    reader.close();
+}
+
diff --git a/benchmarks/run_benchmark_write_pbf.sh b/benchmarks/run_benchmark_write_pbf.sh
new file mode 100755
index 0000000..8143097
--- /dev/null
+++ b/benchmarks/run_benchmark_write_pbf.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+#  run_benchmark_write_pbf.sh
+#
+#  Will read the input file and after reading it into memory completely,
+#  write it to /dev/null. Because this will need the time to read *and* write
+#  the file, it will report the times for reading and writing. You can
+#  subtract the times needed for the "count" benchmark to (roughly) get the
+#  write times.
+#
+
+set -e
+
+BENCHMARK_NAME=write_pbf
+
+. @CMAKE_BINARY_DIR@/benchmarks/setup.sh
+
+CMD=$OB_DIR/osmium_benchmark_$BENCHMARK_NAME
+
+echo "# file size num mem time cpu_kernel cpu_user cpu_percent cmd options"
+for data in $OB_DATA_FILES; do
+    filename=`basename $data`
+    filesize=`stat --format="%s" --dereference $data`
+    for n in $OB_SEQ; do
+        $OB_TIME_CMD -f "$filename $filesize $n $OB_TIME_FORMAT" $CMD $data /dev/null 2>&1 >/dev/null | sed -e "s%$DATA_DIR/%%" | sed -e "s%$OB_DIR/%%"
+    done
+done
+
diff --git a/cmake/FindOSMPBF.cmake b/cmake/FindOSMPBF.cmake
deleted file mode 100644
index deeebd8..0000000
--- a/cmake/FindOSMPBF.cmake
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Locate OSMPBF library
-#
-# This module defines
-#  OSMPBF_FOUND        - if false, do not try to link to OSMPBF
-#  OSMPBF_LIBRARIES    - full library path name
-#  OSMPBF_INCLUDE_DIRS - where to find OSMPBF.hpp
-#
-# Note that the expected include convention is
-#  #include <osmpbf/osmpbf.h>
-# and not
-#  #include <osmpbf.h>
-#
-
-find_path(OSMPBF_INCLUDE_DIR osmpbf/osmpbf.h
-    HINTS $ENV{OSMPBF_DIR}
-    PATH_SUFFIXES include
-    PATHS
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local
-        /usr
-        /opt/local # DarwinPorts
-        /opt
-)
-
-find_library(OSMPBF_LIBRARY
-    NAMES osmpbf
-    HINTS $ENV{OSMPBF_DIR}
-    PATH_SUFFIXES lib64 lib
-    PATHS
-        ~/Library/Frameworks
-        /Library/Frameworks
-        /usr/local
-        /usr
-        /opt/local
-        /opt
-)
-
-# Handle the QUIETLY and REQUIRED arguments and set OSMPBF_FOUND to TRUE if
-# all listed variables are TRUE.
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(OSMPBF DEFAULT_MSG OSMPBF_LIBRARY OSMPBF_INCLUDE_DIR)
-
-# Copy the results to the output variables.
-if(OSMPBF_FOUND)
-    set(OSMPBF_INCLUDE_DIRS ${OSMPBF_INCLUDE_DIR})
-    set(OSMPBF_LIBRARIES ${OSMPBF_LIBRARY})
-endif()
-
diff --git a/cmake/FindOsmium.cmake b/cmake/FindOsmium.cmake
index 56a5a4d..bb14071 100644
--- a/cmake/FindOsmium.cmake
+++ b/cmake/FindOsmium.cmake
@@ -110,15 +110,11 @@ endif()
 #----------------------------------------------------------------------
 # Component 'pbf'
 if(Osmium_USE_PBF)
-    find_package(OSMPBF)
-    find_package(Protobuf)
     find_package(ZLIB)
     find_package(Threads)
 
-    if(OSMPBF_FOUND AND PROTOBUF_FOUND AND ZLIB_FOUND AND Threads_FOUND)
+    if(ZLIB_FOUND AND Threads_FOUND)
         list(APPEND OSMIUM_PBF_LIBRARIES
-            ${OSMPBF_LIBRARIES}
-            ${PROTOBUF_LITE_LIBRARY}
             ${ZLIB_LIBRARIES}
             ${CMAKE_THREAD_LIBS_INIT}
         )
@@ -126,8 +122,6 @@ if(Osmium_USE_PBF)
             list(APPEND OSMIUM_PBF_LIBRARIES ws2_32)
         endif()
         list(APPEND OSMIUM_INCLUDE_DIRS
-            ${OSMPBF_INCLUDE_DIRS}
-            ${PROTOBUF_INCLUDE_DIR}
             ${ZLIB_INCLUDE_DIR}
         )
     else()
diff --git a/cmake/iwyu.sh b/cmake/iwyu.sh
index d203844..f7d8a15 100755
--- a/cmake/iwyu.sh
+++ b/cmake/iwyu.sh
@@ -10,12 +10,12 @@ cmdline="iwyu -Xiwyu --mapping_file=osmium.imp -std=c++11 -I include"
 
 log=build/iwyu.log
 
+mkdir -p build/check_reports
+
 echo "INCLUDE WHAT YOU USE REPORT:" >$log
 
 allok=yes
 
-mkdir -p build/check_reports
-
 for file in `find include/osmium -name \*.hpp`; do
     mkdir -p `dirname build/check_reports/$file`
     ifile="build/check_reports/${file%.hpp}.iwyu"
diff --git a/examples/osmium_area_test.cpp b/examples/osmium_area_test.cpp
index ee2ba12..f072c5e 100644
--- a/examples/osmium_area_test.cpp
+++ b/examples/osmium_area_test.cpp
@@ -132,7 +132,5 @@ int main(int argc, char* argv[]) {
         }
         std::cerr << "\n";
     }
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_convert.cpp b/examples/osmium_convert.cpp
index 7956e11..4f2ba33 100644
--- a/examples/osmium_convert.cpp
+++ b/examples/osmium_convert.cpp
@@ -106,7 +106,6 @@ int main(int argc, char* argv[]) {
         exit_code = 1;
     }
 
-    google::protobuf::ShutdownProtobufLibrary();
     return exit_code;
 }
 
diff --git a/examples/osmium_count.cpp b/examples/osmium_count.cpp
index e249687..baea153 100644
--- a/examples/osmium_count.cpp
+++ b/examples/osmium_count.cpp
@@ -52,7 +52,5 @@ int main(int argc, char* argv[]) {
     std::cout << "Nodes: "     << handler.nodes << "\n";
     std::cout << "Ways: "      << handler.ways << "\n";
     std::cout << "Relations: " << handler.relations << "\n";
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_create_node_cache.cpp b/examples/osmium_create_node_cache.cpp
index b52bd17..359fa19 100644
--- a/examples/osmium_create_node_cache.cpp
+++ b/examples/osmium_create_node_cache.cpp
@@ -50,8 +50,6 @@ int main(int argc, char* argv[]) {
     osmium::apply(reader, location_handler);
     reader.close();
 
-    google::protobuf::ShutdownProtobufLibrary();
-
     return 0;
 }
 
diff --git a/examples/osmium_debug.cpp b/examples/osmium_debug.cpp
index 6878ed1..365fc72 100644
--- a/examples/osmium_debug.cpp
+++ b/examples/osmium_debug.cpp
@@ -46,7 +46,5 @@ int main(int argc, char* argv[]) {
     }
 
     reader.close();
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_read.cpp b/examples/osmium_read.cpp
index 1bb0299..6536006 100644
--- a/examples/osmium_read.cpp
+++ b/examples/osmium_read.cpp
@@ -26,7 +26,5 @@ int main(int argc, char* argv[]) {
     }
 
     reader.close();
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_serdump.cpp b/examples/osmium_serdump.cpp
index 15dce3c..9ab26e4 100644
--- a/examples/osmium_serdump.cpp
+++ b/examples/osmium_serdump.cpp
@@ -202,7 +202,5 @@ int main(int argc, char* argv[]) {
         map_relation2relation.dump_as_list(fd);
         close(fd);
     }
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_toogr.cpp b/examples/osmium_toogr.cpp
index 6d8ab8d..7c5a965 100644
--- a/examples/osmium_toogr.cpp
+++ b/examples/osmium_toogr.cpp
@@ -234,8 +234,6 @@ int main(int argc, char* argv[]) {
     osmium::apply(reader, location_handler, ogr_handler);
     reader.close();
 
-    google::protobuf::ShutdownProtobufLibrary();
-
     int locations_fd = open("locations.dump", O_WRONLY | O_CREAT, 0644);
     if (locations_fd < 0) {
         throw std::system_error(errno, std::system_category(), "Open failed");
diff --git a/examples/osmium_toogr2.cpp b/examples/osmium_toogr2.cpp
index a966c5e..e1b5056 100644
--- a/examples/osmium_toogr2.cpp
+++ b/examples/osmium_toogr2.cpp
@@ -327,7 +327,5 @@ int main(int argc, char* argv[]) {
         }
         std::cerr << "\n";
     }
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_toogr2_exp.cpp b/examples/osmium_toogr2_exp.cpp
index 474da96..db8d5cf 100644
--- a/examples/osmium_toogr2_exp.cpp
+++ b/examples/osmium_toogr2_exp.cpp
@@ -301,7 +301,5 @@ int main(int argc, char* argv[]) {
         }
         std::cerr << "\n";
     }
-
-    google::protobuf::ShutdownProtobufLibrary();
 }
 
diff --git a/examples/osmium_use_node_cache.cpp b/examples/osmium_use_node_cache.cpp
index d800e8d..cfee6df 100644
--- a/examples/osmium_use_node_cache.cpp
+++ b/examples/osmium_use_node_cache.cpp
@@ -63,8 +63,6 @@ int main(int argc, char* argv[]) {
     osmium::apply(reader, location_handler, handler);
     reader.close();
 
-    google::protobuf::ShutdownProtobufLibrary();
-
     return 0;
 }
 
diff --git a/include/boost_unicode_iterator.hpp b/include/boost_unicode_iterator.hpp
deleted file mode 100644
index 3a7b68f..0000000
--- a/include/boost_unicode_iterator.hpp
+++ /dev/null
@@ -1,776 +0,0 @@
-/*
- *
- * Copyright (c) 2004
- * John Maddock
- *
- * Use, modification and distribution are subject to the 
- * Boost Software License, Version 1.0. (See accompanying file 
- * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- *
- */
- 
- /*
-  *   LOCATION:    see http://www.boost.org for most recent version.
-  *   FILE         unicode_iterator.hpp
-  *   VERSION      see <boost/version.hpp>
-  *   DESCRIPTION: Iterator adapters for converting between different Unicode encodings.
-  */
-
-/****************************************************************************
-
-Contents:
-~~~~~~~~~
-
-1) Read Only, Input Adapters:
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-template <class BaseIterator, class U8Type = ::boost::uint8_t>
-class u32_to_u8_iterator;
-
-Adapts sequence of UTF-32 code points to "look like" a sequence of UTF-8.
-
-template <class BaseIterator, class U32Type = ::boost::uint32_t>
-class u8_to_u32_iterator;
-
-Adapts sequence of UTF-8 code points to "look like" a sequence of UTF-32.
-
-template <class BaseIterator, class U16Type = ::boost::uint16_t>
-class u32_to_u16_iterator;
-
-Adapts sequence of UTF-32 code points to "look like" a sequence of UTF-16.
-
-template <class BaseIterator, class U32Type = ::boost::uint32_t>
-class u16_to_u32_iterator;
-
-Adapts sequence of UTF-16 code points to "look like" a sequence of UTF-32.
-
-2) Single pass output iterator adapters:
-
-template <class BaseIterator>
-class utf8_output_iterator;
-
-Accepts UTF-32 code points and forwards them on as UTF-8 code points.
-
-template <class BaseIterator>
-class utf16_output_iterator;
-
-Accepts UTF-32 code points and forwards them on as UTF-16 code points.
-
-****************************************************************************/
-
-#ifndef BOOST_REGEX_UNICODE_ITERATOR_HPP
-#define BOOST_REGEX_UNICODE_ITERATOR_HPP
-#include <boost/cstdint.hpp>
-#include <boost/assert.hpp>
-#include <boost/iterator/iterator_facade.hpp>
-#include <boost/static_assert.hpp>
-#include <boost/throw_exception.hpp>
-#include <stdexcept>
-#ifndef BOOST_NO_STD_LOCALE
-#include <sstream>
-#include <ios>
-#endif
-#include <limits.h> // CHAR_BIT
-
-namespace boost{
-
-namespace detail{
-
-static const ::boost::uint16_t high_surrogate_base = 0xD7C0u;
-static const ::boost::uint16_t low_surrogate_base = 0xDC00u;
-static const ::boost::uint32_t ten_bit_mask = 0x3FFu;
-
-inline bool is_high_surrogate(::boost::uint16_t v)
-{
-   return (v & 0xFFFFFC00u) == 0xd800u;
-}
-inline bool is_low_surrogate(::boost::uint16_t v)
-{
-   return (v & 0xFFFFFC00u) == 0xdc00u;
-}
-template <class T>
-inline bool is_surrogate(T v)
-{
-   return (v & 0xFFFFF800u) == 0xd800;
-}
-
-inline unsigned utf8_byte_count(boost::uint8_t c)
-{
-   // if the most significant bit with a zero in it is in position
-   // 8-N then there are N bytes in this UTF-8 sequence:
-   boost::uint8_t mask = 0x80u;
-   unsigned result = 0;
-   while(c & mask)
-   {
-      ++result;
-      mask >>= 1;
-   }
-   return (result == 0) ? 1 : ((result > 4) ? 4 : result);
-}
-
-inline unsigned utf8_trailing_byte_count(boost::uint8_t c)
-{
-   return utf8_byte_count(c) - 1;
-}
-
-#ifdef BOOST_MSVC
-#pragma warning(push)
-#pragma warning(disable:4100)
-#endif
-inline void invalid_utf32_code_point(::boost::uint32_t val)
-{
-#ifndef BOOST_NO_STD_LOCALE
-   std::stringstream ss;
-   ss << "Invalid UTF-32 code point U+" << std::showbase << std::hex << val << " encountered while trying to encode UTF-16 sequence";
-   std::out_of_range e(ss.str());
-#else
-   std::out_of_range e("Invalid UTF-32 code point encountered while trying to encode UTF-16 sequence");
-#endif
-   boost::throw_exception(e);
-}
-#ifdef BOOST_MSVC
-#pragma warning(pop)
-#endif
-
-
-} // namespace detail
-
-template <class BaseIterator, class U16Type = ::boost::uint16_t>
-class u32_to_u16_iterator
-   : public boost::iterator_facade<u32_to_u16_iterator<BaseIterator, U16Type>, U16Type, std::bidirectional_iterator_tag, const U16Type>
-{
-   typedef boost::iterator_facade<u32_to_u16_iterator<BaseIterator, U16Type>, U16Type, std::bidirectional_iterator_tag, const U16Type> base_type;
-
-#if !defined(BOOST_NO_STD_ITERATOR_TRAITS) && !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
-   typedef typename std::iterator_traits<BaseIterator>::value_type base_value_type;
-
-   BOOST_STATIC_ASSERT(sizeof(base_value_type)*CHAR_BIT == 32);
-   BOOST_STATIC_ASSERT(sizeof(U16Type)*CHAR_BIT == 16);
-#endif
-
-public:
-   typename base_type::reference
-      dereference()const
-   {
-      if(m_current == 2)
-         extract_current();
-      return m_values[m_current];
-   }
-   bool equal(const u32_to_u16_iterator& that)const
-   {
-      if(m_position == that.m_position)
-      {
-         // Both m_currents must be equal, or both even
-         // this is the same as saying their sum must be even:
-         return (m_current + that.m_current) & 1u ? false : true;
-      }
-      return false;
-   }
-   void increment()
-   {
-      // if we have a pending read then read now, so that we know whether
-      // to skip a position, or move to a low-surrogate:
-      if(m_current == 2)
-      {
-         // pending read:
-         extract_current();
-      }
-      // move to the next surrogate position:
-      ++m_current;
-      // if we've reached the end skip a position:
-      if(m_values[m_current] == 0)
-      {
-         m_current = 2;
-         ++m_position;
-      }
-   }
-   void decrement()
-   {
-      if(m_current != 1)
-      {
-         // decrementing an iterator always leads to a valid position:
-         --m_position;
-         extract_current();
-         m_current = m_values[1] ? 1 : 0;
-      }
-      else
-      {
-         m_current = 0;
-      }
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-   // construct:
-   u32_to_u16_iterator() : m_position(), m_current(0)
-   {
-      m_values[0] = 0;
-      m_values[1] = 0;
-      m_values[2] = 0;
-   }
-   u32_to_u16_iterator(BaseIterator b) : m_position(b), m_current(2)
-   {
-      m_values[0] = 0;
-      m_values[1] = 0;
-      m_values[2] = 0;
-   }
-private:
-
-   void extract_current()const
-   {
-      // begin by checking for a code point out of range:
-      ::boost::uint32_t v = *m_position;
-      if(v >= 0x10000u)
-      {
-         if(v > 0x10FFFFu)
-            detail::invalid_utf32_code_point(*m_position);
-         // split into two surrogates:
-         m_values[0] = static_cast<U16Type>(v >> 10) + detail::high_surrogate_base;
-         m_values[1] = static_cast<U16Type>(v & detail::ten_bit_mask) + detail::low_surrogate_base;
-         m_current = 0;
-         BOOST_ASSERT(detail::is_high_surrogate(m_values[0]));
-         BOOST_ASSERT(detail::is_low_surrogate(m_values[1]));
-      }
-      else
-      {
-         // 16-bit code point:
-         m_values[0] = static_cast<U16Type>(*m_position);
-         m_values[1] = 0;
-         m_current = 0;
-         // value must not be a surrogate:
-         if(detail::is_surrogate(m_values[0]))
-            detail::invalid_utf32_code_point(*m_position);
-      }
-   }
-   BaseIterator m_position;
-   mutable U16Type m_values[3];
-   mutable unsigned m_current;
-};
-
-template <class BaseIterator, class U32Type = ::boost::uint32_t>
-class u16_to_u32_iterator
-   : public boost::iterator_facade<u16_to_u32_iterator<BaseIterator, U32Type>, U32Type, std::bidirectional_iterator_tag, const U32Type>
-{
-   typedef boost::iterator_facade<u16_to_u32_iterator<BaseIterator, U32Type>, U32Type, std::bidirectional_iterator_tag, const U32Type> base_type;
-   // special values for pending iterator reads:
-   BOOST_STATIC_CONSTANT(U32Type, pending_read = 0xffffffffu);
-
-#if !defined(BOOST_NO_STD_ITERATOR_TRAITS) && !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
-   typedef typename std::iterator_traits<BaseIterator>::value_type base_value_type;
-
-   BOOST_STATIC_ASSERT(sizeof(base_value_type)*CHAR_BIT == 16);
-   BOOST_STATIC_ASSERT(sizeof(U32Type)*CHAR_BIT == 32);
-#endif
-
-public:
-   typename base_type::reference
-      dereference()const
-   {
-      if(m_value == pending_read)
-         extract_current();
-      return m_value;
-   }
-   bool equal(const u16_to_u32_iterator& that)const
-   {
-      return m_position == that.m_position;
-   }
-   void increment()
-   {
-      // skip high surrogate first if there is one:
-      if(detail::is_high_surrogate(*m_position)) ++m_position;
-      ++m_position;
-      m_value = pending_read;
-   }
-   void decrement()
-   {
-      --m_position;
-      // if we have a low surrogate then go back one more:
-      if(detail::is_low_surrogate(*m_position)) 
-         --m_position;
-      m_value = pending_read;
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-   // construct:
-   u16_to_u32_iterator() : m_position()
-   {
-      m_value = pending_read;
-   }
-   u16_to_u32_iterator(BaseIterator b) : m_position(b)
-   {
-      m_value = pending_read;
-   }
-   //
-   // Range checked version:
-   //
-   u16_to_u32_iterator(BaseIterator b, BaseIterator start, BaseIterator end) : m_position(b)
-   {
-      m_value = pending_read;
-      //
-      // The range must not start with a low surrogate, or end in a high surrogate,
-      // otherwise we run the risk of running outside the underlying input range.
-      // Likewise b must not be located at a low surrogate.
-      //
-      boost::uint16_t val;
-      if(start != end)
-      {
-         if((b != start) && (b != end))
-         {
-            val = *b;
-            if(detail::is_surrogate(val) && ((val & 0xFC00u) == 0xDC00u))
-               invalid_code_point(val);
-         }
-         val = *start;
-         if(detail::is_surrogate(val) && ((val & 0xFC00u) == 0xDC00u))
-            invalid_code_point(val);
-         val = *--end;
-         if(detail::is_high_surrogate(val))
-            invalid_code_point(val);
-      }
-   }
-private:
-   static void invalid_code_point(::boost::uint16_t val)
-   {
-#ifndef BOOST_NO_STD_LOCALE
-      std::stringstream ss;
-      ss << "Misplaced UTF-16 surrogate U+" << std::showbase << std::hex << val << " encountered while trying to encode UTF-32 sequence";
-      std::out_of_range e(ss.str());
-#else
-      std::out_of_range e("Misplaced UTF-16 surrogate encountered while trying to encode UTF-32 sequence");
-#endif
-      boost::throw_exception(e);
-   }
-   void extract_current()const
-   {
-      m_value = static_cast<U32Type>(static_cast< ::boost::uint16_t>(*m_position));
-      // if the last value is a high surrogate then adjust m_position and m_value as needed:
-      if(detail::is_high_surrogate(*m_position))
-      {
-         // precondition; next value must have be a low-surrogate:
-         BaseIterator next(m_position);
-         ::boost::uint16_t t = *++next;
-         if((t & 0xFC00u) != 0xDC00u)
-            invalid_code_point(t);
-         m_value = (m_value - detail::high_surrogate_base) << 10;
-         m_value |= (static_cast<U32Type>(static_cast< ::boost::uint16_t>(t)) & detail::ten_bit_mask);
-      }
-      // postcondition; result must not be a surrogate:
-      if(detail::is_surrogate(m_value))
-         invalid_code_point(static_cast< ::boost::uint16_t>(m_value));
-   }
-   BaseIterator m_position;
-   mutable U32Type m_value;
-};
-
-template <class BaseIterator, class U8Type = ::boost::uint8_t>
-class u32_to_u8_iterator
-   : public boost::iterator_facade<u32_to_u8_iterator<BaseIterator, U8Type>, U8Type, std::bidirectional_iterator_tag, const U8Type>
-{
-   typedef boost::iterator_facade<u32_to_u8_iterator<BaseIterator, U8Type>, U8Type, std::bidirectional_iterator_tag, const U8Type> base_type;
-   
-#if !defined(BOOST_NO_STD_ITERATOR_TRAITS) && !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
-   typedef typename std::iterator_traits<BaseIterator>::value_type base_value_type;
-
-   BOOST_STATIC_ASSERT(sizeof(base_value_type)*CHAR_BIT == 32);
-   BOOST_STATIC_ASSERT(sizeof(U8Type)*CHAR_BIT == 8);
-#endif
-
-public:
-   typename base_type::reference
-      dereference()const
-   {
-      if(m_current == 4)
-         extract_current();
-      return m_values[m_current];
-   }
-   bool equal(const u32_to_u8_iterator& that)const
-   {
-      if(m_position == that.m_position)
-      {
-         // either the m_current's must be equal, or one must be 0 and 
-         // the other 4: which means neither must have bits 1 or 2 set:
-         return (m_current == that.m_current)
-            || (((m_current | that.m_current) & 3) == 0);
-      }
-      return false;
-   }
-   void increment()
-   {
-      // if we have a pending read then read now, so that we know whether
-      // to skip a position, or move to a low-surrogate:
-      if(m_current == 4)
-      {
-         // pending read:
-         extract_current();
-      }
-      // move to the next surrogate position:
-      ++m_current;
-      // if we've reached the end skip a position:
-      if(m_values[m_current] == 0)
-      {
-         m_current = 4;
-         ++m_position;
-      }
-   }
-   void decrement()
-   {
-      if((m_current & 3) == 0)
-      {
-         --m_position;
-         extract_current();
-         m_current = 3;
-         while(m_current && (m_values[m_current] == 0))
-            --m_current;
-      }
-      else
-         --m_current;
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-   // construct:
-   u32_to_u8_iterator() : m_position(), m_current(0)
-   {
-      m_values[0] = 0;
-      m_values[1] = 0;
-      m_values[2] = 0;
-      m_values[3] = 0;
-      m_values[4] = 0;
-   }
-   u32_to_u8_iterator(BaseIterator b) : m_position(b), m_current(4)
-   {
-      m_values[0] = 0;
-      m_values[1] = 0;
-      m_values[2] = 0;
-      m_values[3] = 0;
-      m_values[4] = 0;
-   }
-private:
-
-   void extract_current()const
-   {
-      boost::uint32_t c = *m_position;
-      if(c > 0x10FFFFu)
-         detail::invalid_utf32_code_point(c);
-      if(c < 0x80u)
-      {
-         m_values[0] = static_cast<unsigned char>(c);
-         m_values[1] = static_cast<unsigned char>(0u);
-         m_values[2] = static_cast<unsigned char>(0u);
-         m_values[3] = static_cast<unsigned char>(0u);
-      }
-      else if(c < 0x800u)
-      {
-         m_values[0] = static_cast<unsigned char>(0xC0u + (c >> 6));
-         m_values[1] = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-         m_values[2] = static_cast<unsigned char>(0u);
-         m_values[3] = static_cast<unsigned char>(0u);
-      }
-      else if(c < 0x10000u)
-      {
-         m_values[0] = static_cast<unsigned char>(0xE0u + (c >> 12));
-         m_values[1] = static_cast<unsigned char>(0x80u + ((c >> 6) & 0x3Fu));
-         m_values[2] = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-         m_values[3] = static_cast<unsigned char>(0u);
-      }
-      else
-      {
-         m_values[0] = static_cast<unsigned char>(0xF0u + (c >> 18));
-         m_values[1] = static_cast<unsigned char>(0x80u + ((c >> 12) & 0x3Fu));
-         m_values[2] = static_cast<unsigned char>(0x80u + ((c >> 6) & 0x3Fu));
-         m_values[3] = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-      }
-      m_current= 0;
-   }
-   BaseIterator m_position;
-   mutable U8Type m_values[5];
-   mutable unsigned m_current;
-};
-
-template <class BaseIterator, class U32Type = ::boost::uint32_t>
-class u8_to_u32_iterator
-   : public boost::iterator_facade<u8_to_u32_iterator<BaseIterator, U32Type>, U32Type, std::bidirectional_iterator_tag, const U32Type>
-{
-   typedef boost::iterator_facade<u8_to_u32_iterator<BaseIterator, U32Type>, U32Type, std::bidirectional_iterator_tag, const U32Type> base_type;
-   // special values for pending iterator reads:
-   BOOST_STATIC_CONSTANT(U32Type, pending_read = 0xffffffffu);
-
-#if !defined(BOOST_NO_STD_ITERATOR_TRAITS) && !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
-   typedef typename std::iterator_traits<BaseIterator>::value_type base_value_type;
-
-   BOOST_STATIC_ASSERT(sizeof(base_value_type)*CHAR_BIT == 8);
-   BOOST_STATIC_ASSERT(sizeof(U32Type)*CHAR_BIT == 32);
-#endif
-
-public:
-   typename base_type::reference
-      dereference()const
-   {
-      if(m_value == pending_read)
-         extract_current();
-      return m_value;
-   }
-   bool equal(const u8_to_u32_iterator& that)const
-   {
-      return m_position == that.m_position;
-   }
-   void increment()
-   {
-      // We must not start with a continuation character:
-      if((static_cast<boost::uint8_t>(*m_position) & 0xC0) == 0x80)
-         invalid_sequence();
-      // skip high surrogate first if there is one:
-      unsigned c = detail::utf8_byte_count(*m_position);
-      if(m_value == pending_read)
-      {
-         // Since we haven't read in a value, we need to validate the code points:
-         for(unsigned i = 0; i < c; ++i)
-         {
-            ++m_position;
-            // We must have a continuation byte:
-            if((i != c - 1) && ((static_cast<boost::uint8_t>(*m_position) & 0xC0) != 0x80))
-               invalid_sequence();
-         }
-      }
-      else
-      {
-         std::advance(m_position, c);
-      }
-      m_value = pending_read;
-   }
-   void decrement()
-   {
-      // Keep backtracking until we don't have a trailing character:
-      unsigned count = 0;
-      while((*--m_position & 0xC0u) == 0x80u) ++count;
-      // now check that the sequence was valid:
-      if(count != detail::utf8_trailing_byte_count(*m_position))
-         invalid_sequence();
-      m_value = pending_read;
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-   // construct:
-   u8_to_u32_iterator() : m_position()
-   {
-      m_value = pending_read;
-   }
-   u8_to_u32_iterator(BaseIterator b) : m_position(b)
-   {
-      m_value = pending_read;
-   }
-   //
-   // Checked constructor:
-   //
-   u8_to_u32_iterator(BaseIterator b, BaseIterator start, BaseIterator end) : m_position(b)
-   {
-      m_value = pending_read;
-      //
-      // We must not start with a continuation character, or end with a 
-      // truncated UTF-8 sequence otherwise we run the risk of going past
-      // the start/end of the underlying sequence:
-      //
-      if(start != end)
-      {
-         unsigned char v = *start;
-         if((v & 0xC0u) == 0x80u)
-            invalid_sequence();
-         if((b != start) && (b != end) && ((*b & 0xC0u) == 0x80u))
-            invalid_sequence();
-         BaseIterator pos = end;
-         do
-         {
-            v = *--pos;
-         }
-         while((start != pos) && ((v & 0xC0u) == 0x80u));
-         std::ptrdiff_t extra = detail::utf8_byte_count(v);
-         if(std::distance(pos, end) < extra)
-            invalid_sequence();
-      }
-   }
-private:
-   static void invalid_sequence()
-   {
-      std::out_of_range e("Invalid UTF-8 sequence encountered while trying to encode UTF-32 character");
-      boost::throw_exception(e);
-   }
-   void extract_current()const
-   {
-      m_value = static_cast<U32Type>(static_cast< ::boost::uint8_t>(*m_position));
-      // we must not have a continuation character:
-      if((m_value & 0xC0u) == 0x80u)
-         invalid_sequence();
-      // see how many extra bytes we have:
-      unsigned extra = detail::utf8_trailing_byte_count(*m_position);
-      // extract the extra bits, 6 from each extra byte:
-      BaseIterator next(m_position);
-      for(unsigned c = 0; c < extra; ++c)
-      {
-         ++next;
-         m_value <<= 6;
-         // We must have a continuation byte:
-         if((static_cast<boost::uint8_t>(*next) & 0xC0) != 0x80)
-            invalid_sequence();
-         m_value += static_cast<boost::uint8_t>(*next) & 0x3Fu;
-      }
-      // we now need to remove a few of the leftmost bits, but how many depends
-      // upon how many extra bytes we've extracted:
-      static const boost::uint32_t masks[4] = 
-      {
-         0x7Fu,
-         0x7FFu,
-         0xFFFFu,
-         0x1FFFFFu,
-      };
-      m_value &= masks[extra];
-      // check the result:
-      if(m_value > static_cast<U32Type>(0x10FFFFu))
-         invalid_sequence();
-   }
-   BaseIterator m_position;
-   mutable U32Type m_value;
-};
-
-template <class BaseIterator>
-class utf16_output_iterator
-{
-public:
-   typedef void                                   difference_type;
-   typedef void                                   value_type;
-   typedef boost::uint32_t*                       pointer;
-   typedef boost::uint32_t&                       reference;
-   typedef std::output_iterator_tag               iterator_category;
-
-   utf16_output_iterator(const BaseIterator& b)
-      : m_position(b){}
-   utf16_output_iterator(const utf16_output_iterator& that)
-      : m_position(that.m_position){}
-   utf16_output_iterator& operator=(const utf16_output_iterator& that)
-   {
-      m_position = that.m_position;
-      return *this;
-   }
-   const utf16_output_iterator& operator*()const
-   {
-      return *this;
-   }
-   void operator=(boost::uint32_t val)const
-   {
-      push(val);
-   }
-   utf16_output_iterator& operator++()
-   {
-      return *this;
-   }
-   utf16_output_iterator& operator++(int)
-   {
-      return *this;
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-private:
-   void push(boost::uint32_t v)const
-   {
-      if(v >= 0x10000u)
-      {
-         // begin by checking for a code point out of range:
-         if(v > 0x10FFFFu)
-            detail::invalid_utf32_code_point(v);
-         // split into two surrogates:
-         *m_position++ = static_cast<boost::uint16_t>(v >> 10) + detail::high_surrogate_base;
-         *m_position++ = static_cast<boost::uint16_t>(v & detail::ten_bit_mask) + detail::low_surrogate_base;
-      }
-      else
-      {
-         // 16-bit code point:
-         // value must not be a surrogate:
-         if(detail::is_surrogate(v))
-            detail::invalid_utf32_code_point(v);
-         *m_position++ = static_cast<boost::uint16_t>(v);
-      }
-   }
-   mutable BaseIterator m_position;
-};
-
-template <class BaseIterator>
-class utf8_output_iterator
-{
-public:
-   typedef void                                   difference_type;
-   typedef void                                   value_type;
-   typedef boost::uint32_t*                       pointer;
-   typedef boost::uint32_t&                       reference;
-   typedef std::output_iterator_tag               iterator_category;
-
-   utf8_output_iterator(const BaseIterator& b)
-      : m_position(b){}
-   utf8_output_iterator(const utf8_output_iterator& that)
-      : m_position(that.m_position){}
-   utf8_output_iterator& operator=(const utf8_output_iterator& that)
-   {
-      m_position = that.m_position;
-      return *this;
-   }
-   const utf8_output_iterator& operator*()const
-   {
-      return *this;
-   }
-   void operator=(boost::uint32_t val)const
-   {
-      push(val);
-   }
-   utf8_output_iterator& operator++()
-   {
-      return *this;
-   }
-   utf8_output_iterator& operator++(int)
-   {
-      return *this;
-   }
-   BaseIterator base()const
-   {
-      return m_position;
-   }
-private:
-   void push(boost::uint32_t c)const
-   {
-      if(c > 0x10FFFFu)
-         detail::invalid_utf32_code_point(c);
-      if(c < 0x80u)
-      {
-         *m_position++ = static_cast<unsigned char>(c);
-      }
-      else if(c < 0x800u)
-      {
-         *m_position++ = static_cast<unsigned char>(0xC0u + (c >> 6));
-         *m_position++ = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-      }
-      else if(c < 0x10000u)
-      {
-         *m_position++ = static_cast<unsigned char>(0xE0u + (c >> 12));
-         *m_position++ = static_cast<unsigned char>(0x80u + ((c >> 6) & 0x3Fu));
-         *m_position++ = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-      }
-      else
-      {
-         *m_position++ = static_cast<unsigned char>(0xF0u + (c >> 18));
-         *m_position++ = static_cast<unsigned char>(0x80u + ((c >> 12) & 0x3Fu));
-         *m_position++ = static_cast<unsigned char>(0x80u + ((c >> 6) & 0x3Fu));
-         *m_position++ = static_cast<unsigned char>(0x80u + (c & 0x3Fu));
-      }
-   }
-   mutable BaseIterator m_position;
-};
-
-} // namespace boost
-
-#endif // BOOST_REGEX_UNICODE_ITERATOR_HPP
-
diff --git a/include/osmium/area/multipolygon_collector.hpp b/include/osmium/area/multipolygon_collector.hpp
index 2cb8fe1..bf2a4ce 100644
--- a/include/osmium/area/multipolygon_collector.hpp
+++ b/include/osmium/area/multipolygon_collector.hpp
@@ -42,6 +42,7 @@ DEALINGS IN THE SOFTWARE.
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
+#include <osmium/osm/node_ref.hpp>
 #include <osmium/osm/relation.hpp>
 #include <osmium/osm/tag.hpp>
 #include <osmium/osm/way.hpp>
diff --git a/include/osmium/builder/builder.hpp b/include/osmium/builder/builder.hpp
index dcb95e2..4424d88 100644
--- a/include/osmium/builder/builder.hpp
+++ b/include/osmium/builder/builder.hpp
@@ -147,6 +147,7 @@ namespace osmium {
              * @param length Length of data in bytes. If data is a
              *               \0-terminated string, length must contain the
              *               \0 byte.
+             * @returns The number of bytes appended (length).
              */
             osmium::memory::item_size_type append(const char* data, const osmium::memory::item_size_type length) {
                 unsigned char* target = m_buffer.reserve_space(length);
@@ -156,11 +157,24 @@ namespace osmium {
 
             /**
              * Append \0-terminated string to buffer.
+             *
+             * @param str \0-terminated string.
+             * @returns The number of bytes appended (strlen(str) + 1).
              */
             osmium::memory::item_size_type append(const char* str) {
                 return append(str, static_cast<osmium::memory::item_size_type>(std::strlen(str) + 1));
             }
 
+            /**
+             * Append '\0' to the buffer.
+             *
+             * @returns The number of bytes appended (always 1).
+             */
+            osmium::memory::item_size_type append_zero() {
+                *m_buffer.reserve_space(1) = '\0';
+                return 1;
+            }
+
             /// Return the buffer this builder is using.
             osmium::memory::Buffer& buffer() noexcept {
                 return m_buffer;
@@ -188,11 +202,11 @@ namespace osmium {
              * Add user name to buffer.
              *
              * @param user Pointer to user name.
-             * @param length Length of user name including \0 byte.
+             * @param length Length of user name (without \0 termination).
              */
             void add_user(const char* user, const string_size_type length) {
-                object().set_user_size(length);
-                add_size(append(user, length));
+                object().set_user_size(length + 1);
+                add_size(append(user, length) + append_zero());
                 add_padding(true);
             }
 
@@ -202,7 +216,7 @@ namespace osmium {
              * @param user Pointer to \0-terminated user name.
              */
             void add_user(const char* user) {
-                add_user(user, static_cast_with_assert<string_size_type>(std::strlen(user) + 1));
+                add_user(user, static_cast_with_assert<string_size_type>(std::strlen(user)));
             }
 
             /**
@@ -211,7 +225,7 @@ namespace osmium {
              * @param user User name.
              */
             void add_user(const std::string& user) {
-                add_user(user.data(), static_cast_with_assert<string_size_type>(user.size() + 1));
+                add_user(user.data(), static_cast_with_assert<string_size_type>(user.size()));
             }
 
         }; // class ObjectBuilder
diff --git a/include/osmium/builder/osm_object_builder.hpp b/include/osmium/builder/osm_object_builder.hpp
index 058f89e..074076c 100644
--- a/include/osmium/builder/osm_object_builder.hpp
+++ b/include/osmium/builder/osm_object_builder.hpp
@@ -72,8 +72,8 @@ namespace osmium {
             /**
              * Add tag to buffer.
              *
-             * @param key Tag key.
-             * @param value Tag value.
+             * @param key Tag key (0-terminated string).
+             * @param value Tag value (0-terminated string).
              */
             void add_tag(const char* key, const char* value) {
                 add_size(append(key) + append(value));
@@ -82,6 +82,18 @@ namespace osmium {
             /**
              * Add tag to buffer.
              *
+             * @param key Pointer to tag key.
+             * @param key_length Length of key (not including the \0 byte).
+             * @param value Pointer to tag value.
+             * @param value_length Length of value (not including the \0 byte).
+             */
+            void add_tag(const char* key, const string_size_type key_length, const char* value, const string_size_type value_length) {
+                add_size(append(key, key_length) + append_zero() + append(value, value_length) + append_zero());
+            }
+
+            /**
+             * Add tag to buffer.
+             *
              * @param key Tag key.
              * @param value Tag value.
              */
@@ -128,11 +140,11 @@ namespace osmium {
              * @param member Relation member object where the length of the role
              *               will be set.
              * @param role The role.
-             * @param length Length of role string including \0 termination.
+             * @param length Length of role (without \0 termination).
              */
             void add_role(osmium::RelationMember& member, const char* role, const string_size_type length) {
-                member.set_role_size(length);
-                add_size(append(role, length));
+                member.set_role_size(length + 1);
+                add_size(append(role, length) + append_zero());
                 add_padding(true);
             }
 
@@ -144,7 +156,7 @@ namespace osmium {
              * @param role \0-terminated role.
              */
             void add_role(osmium::RelationMember& member, const char* role) {
-                add_role(member, role, static_cast_with_assert<string_size_type>(std::strlen(role) + 1));
+                add_role(member, role, static_cast_with_assert<string_size_type>(std::strlen(role)));
             }
 
             /**
@@ -155,7 +167,7 @@ namespace osmium {
              * @param role Role.
              */
             void add_role(osmium::RelationMember& member, const std::string& role) {
-                add_role(member, role.data(), static_cast_with_assert<string_size_type>(role.size() + 1));
+                add_role(member, role.data(), static_cast_with_assert<string_size_type>(role.size()));
             }
 
         public:
@@ -174,15 +186,16 @@ namespace osmium {
              * @param type The type (node, way, or relation).
              * @param ref The ID of the member.
              * @param role The role of the member.
+             * @param role_length Length of the role (without \0 termination).
              * @param full_member Optional pointer to the member object. If it
              *                    is available a copy will be added to the
              *                    relation.
              */
-            void add_member(osmium::item_type type, object_id_type ref, const char* role, const osmium::OSMObject* full_member = nullptr) {
+            void add_member(osmium::item_type type, object_id_type ref, const char* role, const string_size_type role_length, const osmium::OSMObject* full_member = nullptr) {
                 osmium::RelationMember* member = reserve_space_for<osmium::RelationMember>();
                 new (member) osmium::RelationMember(ref, type, full_member != nullptr);
                 add_size(sizeof(RelationMember));
-                add_role(*member, role);
+                add_role(*member, role, role_length);
                 if (full_member) {
                     add_item(full_member);
                 }
@@ -193,19 +206,27 @@ namespace osmium {
              *
              * @param type The type (node, way, or relation).
              * @param ref The ID of the member.
+             * @param role The role of the member (\0 terminated string).
+             * @param full_member Optional pointer to the member object. If it
+             *                    is available a copy will be added to the
+             *                    relation.
+             */
+            void add_member(osmium::item_type type, object_id_type ref, const char* role, const osmium::OSMObject* full_member = nullptr) {
+                add_member(type, ref, role, strlen(role), full_member);
+            }
+
+            /**
+             * Add a member to the relation.
+             *
+             * @param type The type (node, way, or relation).
+             * @param ref The ID of the member.
              * @param role The role of the member.
              * @param full_member Optional pointer to the member object. If it
              *                    is available a copy will be added to the
              *                    relation.
              */
             void add_member(osmium::item_type type, object_id_type ref, const std::string& role, const osmium::OSMObject* full_member = nullptr) {
-                osmium::RelationMember* member = reserve_space_for<osmium::RelationMember>();
-                new (member) osmium::RelationMember(ref, type, full_member != nullptr);
-                add_size(sizeof(RelationMember));
-                add_role(*member, role);
-                if (full_member) {
-                    add_item(full_member);
-                }
+                add_member(type, ref, role.data(), role.size(), full_member);
             }
 
         }; // class RelationMemberListBuilder
diff --git a/include/osmium/geom/geos.hpp b/include/osmium/geom/geos.hpp
index d4105b0..771b087 100644
--- a/include/osmium/geom/geos.hpp
+++ b/include/osmium/geom/geos.hpp
@@ -42,6 +42,7 @@ DEALINGS IN THE SOFTWARE.
  * @attention If you include this file, you'll need to link with `libgeos`.
  */
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -82,8 +83,9 @@ namespace osmium {
 
             class GEOSFactoryImpl {
 
-                geos::geom::PrecisionModel m_precision_model;
-                geos::geom::GeometryFactory m_geos_factory;
+                std::unique_ptr<const geos::geom::PrecisionModel> m_precision_model;
+                std::unique_ptr<geos::geom::GeometryFactory> m_our_geos_factory;
+                geos::geom::GeometryFactory* m_geos_factory;
 
                 std::unique_ptr<geos::geom::CoordinateSequence> m_coordinate_sequence;
                 std::vector<std::unique_ptr<geos::geom::LinearRing>> m_rings;
@@ -97,16 +99,23 @@ namespace osmium {
                 typedef std::unique_ptr<geos::geom::MultiPolygon> multipolygon_type;
                 typedef std::unique_ptr<geos::geom::LinearRing>   ring_type;
 
+                explicit GEOSFactoryImpl(geos::geom::GeometryFactory& geos_factory) :
+                    m_precision_model(nullptr),
+                    m_our_geos_factory(nullptr),
+                    m_geos_factory(&geos_factory) {
+                }
+
                 explicit GEOSFactoryImpl(int srid = -1) :
-                    m_precision_model(),
-                    m_geos_factory(&m_precision_model, srid) {
+                    m_precision_model(new geos::geom::PrecisionModel),
+                    m_our_geos_factory(new geos::geom::GeometryFactory(m_precision_model.get(), srid)),
+                    m_geos_factory(m_our_geos_factory.get()) {
                 }
 
                 /* Point */
 
                 point_type make_point(const osmium::geom::Coordinates& xy) const {
                     try {
-                        return point_type(m_geos_factory.createPoint(geos::geom::Coordinate(xy.x, xy.y)));
+                        return point_type(m_geos_factory->createPoint(geos::geom::Coordinate(xy.x, xy.y)));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -116,7 +125,7 @@ namespace osmium {
 
                 void linestring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory.getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -132,7 +141,7 @@ namespace osmium {
 
                 linestring_type linestring_finish(size_t /* num_points */) {
                     try {
-                        return linestring_type(m_geos_factory.createLineString(m_coordinate_sequence.release()));
+                        return linestring_type(m_geos_factory->createLineString(m_coordinate_sequence.release()));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -155,7 +164,7 @@ namespace osmium {
                         std::transform(std::next(m_rings.begin(), 1), m_rings.end(), std::back_inserter(*inner_rings), [](std::unique_ptr<geos::geom::LinearRing>& r) {
                             return r.release();
                         });
-                        m_polygons.emplace_back(m_geos_factory.createPolygon(m_rings[0].release(), inner_rings));
+                        m_polygons.emplace_back(m_geos_factory->createPolygon(m_rings[0].release(), inner_rings));
                         m_rings.clear();
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
@@ -164,7 +173,7 @@ namespace osmium {
 
                 void multipolygon_outer_ring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory.getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -172,7 +181,7 @@ namespace osmium {
 
                 void multipolygon_outer_ring_finish() {
                     try {
-                        m_rings.emplace_back(m_geos_factory.createLinearRing(m_coordinate_sequence.release()));
+                        m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release()));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -180,7 +189,7 @@ namespace osmium {
 
                 void multipolygon_inner_ring_start() {
                     try {
-                        m_coordinate_sequence.reset(m_geos_factory.getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
+                        m_coordinate_sequence.reset(m_geos_factory->getCoordinateSequenceFactory()->create(static_cast<size_t>(0), 2));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -188,7 +197,7 @@ namespace osmium {
 
                 void multipolygon_inner_ring_finish() {
                     try {
-                        m_rings.emplace_back(m_geos_factory.createLinearRing(m_coordinate_sequence.release()));
+                        m_rings.emplace_back(m_geos_factory->createLinearRing(m_coordinate_sequence.release()));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
@@ -209,7 +218,7 @@ namespace osmium {
                             return p.release();
                         });
                         m_polygons.clear();
-                        return multipolygon_type(m_geos_factory.createMultiPolygon(polygons));
+                        return multipolygon_type(m_geos_factory->createMultiPolygon(polygons));
                     } catch (geos::util::GEOSException& e) {
                         THROW(osmium::geos_geometry_error(e.what()));
                     }
diff --git a/include/osmium/geom/wkb.hpp b/include/osmium/geom/wkb.hpp
index 2f32fe3..a290c02 100644
--- a/include/osmium/geom/wkb.hpp
+++ b/include/osmium/geom/wkb.hpp
@@ -37,18 +37,10 @@ DEALINGS IN THE SOFTWARE.
 #include <cstdint>
 #include <string>
 
-// Windows is only available for little endian architectures
-// http://stackoverflow.com/questions/6449468/can-i-safely-assume-that-windows-installations-will-always-be-little-endian
-#if !defined(_WIN32) && !defined(__APPLE__)
-# include <endian.h>
-#else
-# define __LITTLE_ENDIAN 1234
-# define __BYTE_ORDER __LITTLE_ENDIAN
-#endif
-
 #include <osmium/geom/coordinates.hpp>
 #include <osmium/geom/factory.hpp>
 #include <osmium/util/cast.hpp>
+#include <osmium/util/endian.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/index/detail/create_map_with_fd.hpp b/include/osmium/index/detail/create_map_with_fd.hpp
index 29dc1dc..5ccbfc8 100644
--- a/include/osmium/index/detail/create_map_with_fd.hpp
+++ b/include/osmium/index/detail/create_map_with_fd.hpp
@@ -39,8 +39,6 @@ DEALINGS IN THE SOFTWARE.
 #include <fcntl.h>
 #include <stdexcept>
 #include <string>
-#include <sys/stat.h>
-#include <sys/types.h>
 #include <vector>
 
 namespace osmium {
diff --git a/include/osmium/index/detail/mmap_vector_base.hpp b/include/osmium/index/detail/mmap_vector_base.hpp
index 2400f89..9b64768 100644
--- a/include/osmium/index/detail/mmap_vector_base.hpp
+++ b/include/osmium/index/detail/mmap_vector_base.hpp
@@ -34,7 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cstddef>
-#include <new>
+#include <new> // IWYU pragma: keep
 #include <stdexcept>
 
 #include <osmium/util/memory_mapping.hpp>
@@ -62,7 +62,7 @@ namespace osmium {
 
             explicit mmap_vector_base(int fd, size_t capacity, size_t size = 0) :
                 m_size(size),
-                m_mapping(capacity, true, fd) {
+                m_mapping(capacity, osmium::util::MemoryMapping::mapping_mode::write_shared, fd) {
             }
 
             explicit mmap_vector_base(size_t capacity = mmap_vector_size_increment) :
diff --git a/include/osmium/index/detail/mmap_vector_file.hpp b/include/osmium/index/detail/mmap_vector_file.hpp
index 1336003..1dadbcb 100644
--- a/include/osmium/index/detail/mmap_vector_file.hpp
+++ b/include/osmium/index/detail/mmap_vector_file.hpp
@@ -33,10 +33,9 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
-
 #include <osmium/index/detail/mmap_vector_base.hpp>
 #include <osmium/index/detail/tmpfile.hpp>
+#include <osmium/util/file.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/index/map/dense_mmap_array.hpp b/include/osmium/index/map/dense_mmap_array.hpp
index fc60a1e..a912aeb 100644
--- a/include/osmium/index/map/dense_mmap_array.hpp
+++ b/include/osmium/index/map/dense_mmap_array.hpp
@@ -35,7 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #ifdef __linux__
 
-#include <osmium/index/detail/mmap_vector_anon.hpp>
+#include <osmium/index/detail/mmap_vector_anon.hpp> // IWYU pragma: keep
 #include <osmium/index/detail/vector_map.hpp>
 
 #define OSMIUM_HAS_INDEX_MAP_DENSE_MMAP_ARRAY
diff --git a/include/osmium/index/map/sparse_mem_map.hpp b/include/osmium/index/map/sparse_mem_map.hpp
index d053155..2b9048b 100644
--- a/include/osmium/index/map/sparse_mem_map.hpp
+++ b/include/osmium/index/map/sparse_mem_map.hpp
@@ -33,7 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <algorithm>
+#include <algorithm> // IWYU pragma: keep (for std::copy)
 #include <cstddef>
 #include <iterator>
 #include <map>
diff --git a/include/osmium/io/any_input.hpp b/include/osmium/io/any_input.hpp
index 633fab3..d16d069 100644
--- a/include/osmium/io/any_input.hpp
+++ b/include/osmium/io/any_input.hpp
@@ -39,8 +39,8 @@ DEALINGS IN THE SOFTWARE.
  * Include this file if you want to read all kinds of OSM files.
  *
  * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
- *            `libexpat`, `libz`, `libbz2`, and enable multithreading.
+ *            `ws2_32` (Windows only), `libexpat`, `libz`, `libbz2`,
+ *            and enable multithreading.
  */
 
 #include <osmium/io/any_compression.hpp> // IWYU pragma: export
diff --git a/include/osmium/io/any_output.hpp b/include/osmium/io/any_output.hpp
index 63de3ff..990a27b 100644
--- a/include/osmium/io/any_output.hpp
+++ b/include/osmium/io/any_output.hpp
@@ -39,12 +39,13 @@ DEALINGS IN THE SOFTWARE.
  * Include this file if you want to write all kinds of OSM files.
  *
  * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
- *            `libz`, `libbz2`, and enable multithreading.
+ *            `ws2_32` (Windows only), `libz`, `libbz2`, and enable
+ *            multithreading.
  */
 
 #include <osmium/io/any_compression.hpp> // IWYU pragma: export
 
+#include <osmium/io/debug_output.hpp> // IWYU pragma: export
 #include <osmium/io/opl_output.hpp> // IWYU pragma: export
 #include <osmium/io/pbf_output.hpp> // IWYU pragma: export
 #include <osmium/io/xml_output.hpp> // IWYU pragma: export
diff --git a/include/osmium/io/bzip2_compression.hpp b/include/osmium/io/bzip2_compression.hpp
index 7e86c15..e961a87 100644
--- a/include/osmium/io/bzip2_compression.hpp
+++ b/include/osmium/io/bzip2_compression.hpp
@@ -274,11 +274,16 @@ namespace osmium {
 
         namespace {
 
+// we want the register_compression() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
             const bool registered_bzip2_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::bzip2,
                 [](int fd) { return new osmium::io::Bzip2Compressor(fd); },
                 [](int fd) { return new osmium::io::Bzip2Decompressor(fd); },
                 [](const char* buffer, size_t size) { return new osmium::io::Bzip2BufferDecompressor(buffer, size); }
             );
+#pragma GCC diagnostic pop
 
         } // anonymous namespace
 
diff --git a/include/osmium/io/compression.hpp b/include/osmium/io/compression.hpp
index c1f8de2..2529761 100644
--- a/include/osmium/io/compression.hpp
+++ b/include/osmium/io/compression.hpp
@@ -266,11 +266,16 @@ namespace osmium {
 
         namespace {
 
+// we want the register_compression() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
             const bool registered_no_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::none,
                 [](int fd) { return new osmium::io::NoCompressor(fd); },
                 [](int fd) { return new osmium::io::NoDecompressor(fd); },
                 [](const char* buffer, size_t size) { return new osmium::io::NoDecompressor(buffer, size); }
             );
+#pragma GCC diagnostic pop
 
         } // anonymous namespace
 
diff --git a/include/osmium/io/pbf_output.hpp b/include/osmium/io/debug_output.hpp
similarity index 77%
copy from include/osmium/io/pbf_output.hpp
copy to include/osmium/io/debug_output.hpp
index 5f46ede..2836f79 100644
--- a/include/osmium/io/pbf_output.hpp
+++ b/include/osmium/io/debug_output.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_IO_PBF_OUTPUT_HPP
-#define OSMIUM_IO_PBF_OUTPUT_HPP
+#ifndef OSMIUM_IO_DEBUG_OUTPUT_HPP
+#define OSMIUM_IO_DEBUG_OUTPUT_HPP
 
 /*
 
@@ -33,17 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-/**
- * @file
- *
- * Include this file if you want to write OSM PBF files.
- *
- * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
- *            `libz`, and enable multithreading.
- */
-
 #include <osmium/io/writer.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_output_format.hpp> // IWYU pragma: export
+#include <osmium/io/detail/debug_output_format.hpp> // IWYU pragma: export
 
-#endif // OSMIUM_IO_PBF_OUTPUT_HPP
+#endif // OSMIUM_IO_DEBUG_OUTPUT_HPP
diff --git a/include/osmium/io/detail/debug_output_format.hpp b/include/osmium/io/detail/debug_output_format.hpp
new file mode 100644
index 0000000..efecc58
--- /dev/null
+++ b/include/osmium/io/detail/debug_output_format.hpp
@@ -0,0 +1,482 @@
+#ifndef OSMIUM_IO_DETAIL_DEBUG_OUTPUT_FORMAT_HPP
+#define OSMIUM_IO_DETAIL_DEBUG_OUTPUT_FORMAT_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <chrono>
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <future>
+#include <iterator>
+#include <memory>
+#include <ratio>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include <utf8.h>
+
+#include <osmium/handler.hpp>
+#include <osmium/io/detail/output_format.hpp>
+#include <osmium/io/file_format.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/memory/collection.hpp>
+#include <osmium/osm/box.hpp>
+#include <osmium/osm/changeset.hpp>
+#include <osmium/osm/item_type.hpp>
+#include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
+#include <osmium/osm/object.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/tag.hpp>
+#include <osmium/osm/timestamp.hpp>
+#include <osmium/osm/way.hpp>
+#include <osmium/thread/pool.hpp>
+#include <osmium/util/minmax.hpp>
+#include <osmium/visitor.hpp>
+
+namespace osmium {
+
+    namespace io {
+
+        class File;
+
+        namespace detail {
+
+            constexpr const char* color_bold    = "\x1b[1m";
+            constexpr const char* color_black   = "\x1b[30m";
+            constexpr const char* color_gray    = "\x1b[30;1m";
+            constexpr const char* color_red     = "\x1b[31m";
+            constexpr const char* color_green   = "\x1b[32m";
+            constexpr const char* color_yellow  = "\x1b[33m";
+            constexpr const char* color_blue    = "\x1b[34m";
+            constexpr const char* color_magenta = "\x1b[35m";
+            constexpr const char* color_cyan    = "\x1b[36m";
+            constexpr const char* color_white   = "\x1b[37m";
+            constexpr const char* color_reset   = "\x1b[0m";
+
+            /**
+             * Writes out one buffer with OSM data in Debug format.
+             */
+            class DebugOutputBlock : public osmium::handler::Handler {
+
+                static constexpr size_t tmp_buffer_size = 50;
+
+                std::shared_ptr<osmium::memory::Buffer> m_input_buffer;
+
+                std::shared_ptr<std::string> m_out;
+
+                char m_tmp_buffer[tmp_buffer_size+1];
+
+                bool m_add_metadata;
+                bool m_use_color;
+
+                template <typename... TArgs>
+                void output_formatted(const char* format, TArgs&&... args) {
+#ifndef NDEBUG
+                    int len =
+#endif
+#ifndef _MSC_VER
+                    snprintf(m_tmp_buffer, tmp_buffer_size, format, std::forward<TArgs>(args)...);
+#else
+                    _snprintf(m_tmp_buffer, tmp_buffer_size, format, std::forward<TArgs>(args)...);
+#endif
+                    assert(len > 0 && static_cast<size_t>(len) < tmp_buffer_size);
+                    *m_out += m_tmp_buffer;
+                }
+
+                void append_encoded_string(const char* data) {
+                    const char* end = data + std::strlen(data);
+
+                    while (data != end) {
+                        const char* last = data;
+                        uint32_t c = utf8::next(data, end);
+
+                        // This is a list of Unicode code points that we let
+                        // through instead of escaping them. It is incomplete
+                        // and can be extended later.
+                        // Generally we don't want to let through any
+                        // non-printing characters.
+                        if ((0x0020 <= c && c <= 0x0021) ||
+                            (0x0023 <= c && c <= 0x003b) ||
+                            (0x003d == c) ||
+                            (0x003f <= c && c <= 0x007e) ||
+                            (0x00a1 <= c && c <= 0x00ac) ||
+                            (0x00ae <= c && c <= 0x05ff)) {
+                            m_out->append(last, data);
+                        } else {
+                            write_color(color_red);
+                            output_formatted("<U+%04X>", c);
+                            write_color(color_blue);
+                        }
+                    }
+                }
+
+                void write_color(const char* color) {
+                    if (m_use_color) {
+                        *m_out += color;
+                    }
+                }
+
+                void write_string(const char* string) {
+                    *m_out += '"';
+                    write_color(color_blue);
+                    append_encoded_string(string);
+                    write_color(color_reset);
+                    *m_out += '"';
+                }
+
+                void write_object_type(const char* object_type, bool visible = true) {
+                    if (visible) {
+                        write_color(color_bold);
+                    } else {
+                        write_color(color_white);
+                    }
+                    *m_out += object_type;
+                    write_color(color_reset);
+                    *m_out += ' ';
+                }
+
+                void write_fieldname(const char* name) {
+                    *m_out += "  ";
+                    write_color(color_cyan);
+                    *m_out += name;
+                    write_color(color_reset);
+                    *m_out += ": ";
+                }
+
+                void write_error(const char* msg) {
+                    write_color(color_red);
+                    *m_out += msg;
+                    write_color(color_reset);
+                }
+
+                void write_meta(const osmium::OSMObject& object) {
+                    output_formatted("%" PRId64 "\n", object.id());
+                    if (m_add_metadata) {
+                        write_fieldname("version");
+                        output_formatted("  %d", object.version());
+                        if (object.visible()) {
+                            *m_out += " visible\n";
+                        } else {
+                            write_error(" deleted\n");
+                        }
+                        write_fieldname("changeset");
+                        output_formatted("%d\n", object.changeset());
+                        write_fieldname("timestamp");
+                        *m_out += object.timestamp().to_iso();
+                        output_formatted(" (%d)\n", object.timestamp());
+                        write_fieldname("user");
+                        output_formatted("     %d ", object.uid());
+                        write_string(object.user());
+                        *m_out += '\n';
+                    }
+                }
+
+                void write_tags(const osmium::TagList& tags, const char* padding="") {
+                    if (!tags.empty()) {
+                        write_fieldname("tags");
+                        *m_out += padding;
+                        output_formatted("     %d\n", tags.size());
+
+                        osmium::max_op<int> max;
+                        for (const auto& tag : tags) {
+                            max.update(std::strlen(tag.key()));
+                        }
+                        for (const auto& tag : tags) {
+                            *m_out += "    ";
+                            write_string(tag.key());
+                            int spacing = max() - std::strlen(tag.key());
+                            while (spacing--) {
+                                *m_out += " ";
+                            }
+                            *m_out += " = ";
+                            write_string(tag.value());
+                            *m_out += '\n';
+                        }
+                    }
+                }
+
+                void write_location(const osmium::Location& location) {
+                    write_fieldname("lon/lat");
+                    output_formatted("  %.7f,%.7f", location.lon_without_check(), location.lat_without_check());
+                    if (!location.valid()) {
+                        write_error(" INVALID LOCATION!");
+                    }
+                    *m_out += '\n';
+                }
+
+                void write_box(const osmium::Box& box) {
+                    write_fieldname("box l/b/r/t");
+                    if (!box) {
+                        write_error("BOX NOT SET!\n");
+                        return;
+                    }
+                    const auto& bl = box.bottom_left();
+                    const auto& tr = box.top_right();
+                    output_formatted("%.7f,%.7f %.7f,%.7f", bl.lon_without_check(), bl.lat_without_check(), tr.lon_without_check(), tr.lat_without_check());
+                    if (!box.valid()) {
+                        write_error(" INVALID BOX!");
+                    }
+                    *m_out += '\n';
+                }
+
+            public:
+
+                explicit DebugOutputBlock(osmium::memory::Buffer&& buffer, bool add_metadata, bool use_color) :
+                    m_input_buffer(std::make_shared<osmium::memory::Buffer>(std::move(buffer))),
+                    m_out(std::make_shared<std::string>()),
+                    m_tmp_buffer(),
+                    m_add_metadata(add_metadata),
+                    m_use_color(use_color) {
+                }
+
+                DebugOutputBlock(const DebugOutputBlock&) = default;
+                DebugOutputBlock& operator=(const DebugOutputBlock&) = default;
+
+                DebugOutputBlock(DebugOutputBlock&&) = default;
+                DebugOutputBlock& operator=(DebugOutputBlock&&) = default;
+
+                ~DebugOutputBlock() = default;
+
+                std::string operator()() {
+                    osmium::apply(m_input_buffer->cbegin(), m_input_buffer->cend(), *this);
+
+                    std::string out;
+                    std::swap(out, *m_out);
+                    return out;
+                }
+
+                void node(const osmium::Node& node) {
+                    write_object_type("node", node.visible());
+                    write_meta(node);
+
+                    if (node.visible()) {
+                        write_location(node.location());
+                    }
+
+                    write_tags(node.tags());
+
+                    *m_out += '\n';
+                }
+
+                void way(const osmium::Way& way) {
+                    write_object_type("way", way.visible());
+                    write_meta(way);
+                    write_tags(way.tags());
+
+                    write_fieldname("nodes");
+
+                    output_formatted("    %d", way.nodes().size());
+                    if (way.nodes().size() < 2) {
+                        write_error(" LESS THAN 2 NODES!\n");
+                    } else if (way.nodes().size() > 2000) {
+                        write_error(" MORE THAN 2000 NODES!\n");
+                    } else if (way.nodes().is_closed()) {
+                        *m_out += " (closed)\n";
+                    } else {
+                        *m_out += " (open)\n";
+                    }
+
+                    int width = int(log10(way.nodes().size())) + 1;
+                    int n = 0;
+                    for (const auto& node_ref : way.nodes()) {
+                        output_formatted("    %0*d: %10" PRId64, width, n++, node_ref.ref());
+                        if (node_ref.location().valid()) {
+                            output_formatted(" (%.7f,%.7f)", node_ref.location().lon_without_check(), node_ref.location().lat_without_check());
+                        }
+                        *m_out += '\n';
+                    }
+
+                    *m_out += '\n';
+                }
+
+                void relation(const osmium::Relation& relation) {
+                    static const char* short_typename[] = { "node", "way ", "rel " };
+                    write_object_type("relation", relation.visible());
+                    write_meta(relation);
+                    write_tags(relation.tags());
+
+                    write_fieldname("members");
+                    output_formatted("  %d\n", relation.members().size());
+
+                    int width = int(log10(relation.members().size())) + 1;
+                    int n = 0;
+                    for (const auto& member : relation.members()) {
+                        output_formatted("    %0*d: ", width, n++);
+                        *m_out += short_typename[item_type_to_nwr_index(member.type())];
+                        output_formatted(" %10" PRId64 " ", member.ref());
+                        write_string(member.role());
+                        *m_out += '\n';
+                    }
+
+                    *m_out += '\n';
+                }
+
+                void changeset(const osmium::Changeset& changeset) {
+                    write_object_type("changeset");
+                    output_formatted("%d\n", changeset.id());
+                    write_fieldname("num changes");
+                    output_formatted("%d", changeset.num_changes());
+                    if (changeset.num_changes() == 0) {
+                        write_error(" NO CHANGES!");
+                    }
+                    *m_out += '\n';
+                    write_fieldname("created at");
+                    *m_out += ' ';
+                    *m_out += changeset.created_at().to_iso();
+                    output_formatted(" (%d)\n", changeset.created_at());
+                    write_fieldname("closed at");
+                    *m_out += "  ";
+                    if (changeset.closed()) {
+                        *m_out += changeset.closed_at().to_iso();
+                        output_formatted(" (%d)\n", changeset.closed_at());
+                    } else {
+                        write_error("OPEN!\n");
+                    }
+                    write_fieldname("user");
+                    output_formatted("       %d ", changeset.uid());
+                    write_string(changeset.user());
+                    *m_out += '\n';
+
+                    write_box(changeset.bounds());
+                    write_tags(changeset.tags(), "  ");
+
+                    *m_out += '\n';
+                }
+
+            }; // DebugOutputBlock
+
+            class DebugOutputFormat : public osmium::io::detail::OutputFormat {
+
+                bool m_add_metadata;
+                bool m_use_color;
+
+            public:
+
+                DebugOutputFormat(const osmium::io::File& file, data_queue_type& output_queue) :
+                    OutputFormat(file, output_queue),
+                    m_add_metadata(file.get("add_metadata") != "false"),
+                    m_use_color(file.get("color") == "true") {
+                }
+
+                DebugOutputFormat(const DebugOutputFormat&) = delete;
+                DebugOutputFormat& operator=(const DebugOutputFormat&) = delete;
+
+                void write_buffer(osmium::memory::Buffer&& buffer) override final {
+                    m_output_queue.push(osmium::thread::Pool::instance().submit(DebugOutputBlock{std::move(buffer), m_add_metadata, m_use_color}));
+                }
+
+                void write_fieldname(std::string& out, const char* name) {
+                    out += "  ";
+                    if (m_use_color) {
+                        out += color_cyan;
+                    }
+                    out += name;
+                    if (m_use_color) {
+                        out += color_reset;
+                    }
+                    out += ": ";
+                }
+
+                void write_header(const osmium::io::Header& header) override final {
+                    std::string out;
+
+                    if (m_use_color) {
+                        out += color_bold;
+                    }
+                    out += "header\n";
+                    if (m_use_color) {
+                        out += color_reset;
+                    }
+
+                    write_fieldname(out, "multiple object versions");
+                    out += header.has_multiple_object_versions() ? "yes" : "no";
+                    out += '\n';
+                    write_fieldname(out, "bounding boxes");
+                    out += '\n';
+                    for (const auto& box : header.boxes()) {
+                        out += "    ";
+                        box.bottom_left().as_string(std::back_inserter(out), ',');
+                        out += " ";
+                        box.top_right().as_string(std::back_inserter(out), ',');
+                        out += '\n';
+                    }
+                    write_fieldname(out, "options");
+                    out += '\n';
+                    for (const auto& opt : header) {
+                        out += "    ";
+                        out += opt.first;
+                        out += " = ";
+                        out += opt.second;
+                        out += '\n';
+                    }
+                    out += "\n=============================================\n\n";
+
+                    std::promise<std::string> promise;
+                    m_output_queue.push(promise.get_future());
+                    promise.set_value(std::move(out));
+                }
+
+                void close() override final {
+                    std::string out;
+                    std::promise<std::string> promise;
+                    m_output_queue.push(promise.get_future());
+                    promise.set_value(out);
+                }
+
+            }; // class DebugOutputFormat
+
+            namespace {
+
+// we want the register_output_format() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+                const bool registered_debug_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::debug,
+                    [](const osmium::io::File& file, data_queue_type& output_queue) {
+                        return new osmium::io::detail::DebugOutputFormat(file, output_queue);
+                });
+#pragma GCC diagnostic pop
+
+            } // anonymous namespace
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+#endif // OSMIUM_IO_DETAIL_DEBUG_OUTPUT_FORMAT_HPP
diff --git a/include/osmium/io/detail/opl_output_format.hpp b/include/osmium/io/detail/opl_output_format.hpp
index cf92e13..a3103d9 100644
--- a/include/osmium/io/detail/opl_output_format.hpp
+++ b/include/osmium/io/detail/opl_output_format.hpp
@@ -46,23 +46,7 @@ DEALINGS IN THE SOFTWARE.
 #include <thread>
 #include <utility>
 
-#include <boost/version.hpp>
-
-#ifdef __clang__
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wmissing-noreturn"
-# pragma clang diagnostic ignored "-Wsign-conversion"
-#endif
-
-#if BOOST_VERSION >= 104800
-# include <boost/regex/pending/unicode_iterator.hpp>
-#else
-# include <boost_unicode_iterator.hpp>
-#endif
-
-#ifdef __clang__
-# pragma clang diagnostic pop
-#endif
+#include <utf8.h>
 
 #include <osmium/handler.hpp>
 #include <osmium/io/detail/output_format.hpp>
@@ -103,6 +87,8 @@ namespace osmium {
 
                 char m_tmp_buffer[tmp_buffer_size+1];
 
+                bool m_add_metadata;
+
                 template <typename... TArgs>
                 void output_formatted(const char* format, TArgs&&... args) {
 #ifndef NDEBUG
@@ -117,13 +103,12 @@ namespace osmium {
                     *m_out += m_tmp_buffer;
                 }
 
-                void append_encoded_string(const std::string& data) {
-                    boost::u8_to_u32_iterator<std::string::const_iterator> it(data.cbegin(), data.cbegin(), data.cend());
-                    boost::u8_to_u32_iterator<std::string::const_iterator> end(data.cend(), data.cend(), data.cend());
-                    boost::utf8_output_iterator<std::back_insert_iterator<std::string>> oit(std::back_inserter(*m_out));
+                void append_encoded_string(const char* data) {
+                    const char* end = data + std::strlen(data);
 
-                    for (; it != end; ++it) {
-                        uint32_t c = *it;
+                    while (data != end) {
+                        const char* last = data;
+                        uint32_t c = utf8::next(data, end);
 
                         // This is a list of Unicode code points that we let
                         // through instead of escaping them. It is incomplete
@@ -138,21 +123,29 @@ namespace osmium {
                             (0x0041 <= c && c <= 0x007e) ||
                             (0x00a1 <= c && c <= 0x00ac) ||
                             (0x00ae <= c && c <= 0x05ff)) {
-                            *oit = c;
+                            m_out->append(last, data);
                         } else {
                             *m_out += '%';
-                            output_formatted("%04x", c);
+                            if (c <= 0xff) {
+                                output_formatted("%02x", c);
+                            } else {
+                                output_formatted("%04x", c);
+                            }
+                            *m_out += '%';
                         }
                     }
                 }
 
                 void write_meta(const osmium::OSMObject& object) {
-                    output_formatted("%" PRId64 " v%d d", object.id(), object.version());
-                    *m_out += (object.visible() ? 'V' : 'D');
-                    output_formatted(" c%d t", object.changeset());
-                    *m_out += object.timestamp().to_iso();
-                    output_formatted(" i%d u", object.uid());
-                    append_encoded_string(object.user());
+                    output_formatted("%" PRId64, object.id());
+                    if (m_add_metadata) {
+                        output_formatted(" v%d d", object.version());
+                        *m_out += (object.visible() ? 'V' : 'D');
+                        output_formatted(" c%d t", object.changeset());
+                        *m_out += object.timestamp().to_iso();
+                        output_formatted(" i%d u", object.uid());
+                        append_encoded_string(object.user());
+                    }
                     *m_out += " T";
                     bool first = true;
                     for (const auto& tag : object.tags()) {
@@ -180,10 +173,11 @@ namespace osmium {
 
             public:
 
-                explicit OPLOutputBlock(osmium::memory::Buffer&& buffer) :
+                explicit OPLOutputBlock(osmium::memory::Buffer&& buffer, bool add_metadata) :
                     m_input_buffer(std::make_shared<osmium::memory::Buffer>(std::move(buffer))),
                     m_out(std::make_shared<std::string>()),
-                    m_tmp_buffer() {
+                    m_tmp_buffer(),
+                    m_add_metadata(add_metadata) {
                 }
 
                 OPLOutputBlock(const OPLOutputBlock&) = default;
@@ -240,7 +234,7 @@ namespace osmium {
                         }
                         *m_out += item_type_to_char(member.type());
                         output_formatted("%" PRId64 "@", member.ref());
-                        *m_out += member.role();
+                        append_encoded_string(member.role());
                     }
                     *m_out += '\n';
                 }
@@ -274,17 +268,20 @@ namespace osmium {
 
             class OPLOutputFormat : public osmium::io::detail::OutputFormat {
 
-                OPLOutputFormat(const OPLOutputFormat&) = delete;
-                OPLOutputFormat& operator=(const OPLOutputFormat&) = delete;
+                bool m_add_metadata;
 
             public:
 
                 OPLOutputFormat(const osmium::io::File& file, data_queue_type& output_queue) :
-                    OutputFormat(file, output_queue) {
+                    OutputFormat(file, output_queue),
+                    m_add_metadata(file.get("add_metadata") != "false") {
                 }
 
+                OPLOutputFormat(const OPLOutputFormat&) = delete;
+                OPLOutputFormat& operator=(const OPLOutputFormat&) = delete;
+
                 void write_buffer(osmium::memory::Buffer&& buffer) override final {
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(OPLOutputBlock{std::move(buffer)}));
+                    m_output_queue.push(osmium::thread::Pool::instance().submit(OPLOutputBlock{std::move(buffer), m_add_metadata}));
                 }
 
                 void close() override final {
@@ -298,6 +295,8 @@ namespace osmium {
 
             namespace {
 
+// we want the register_output_format() function to run, setting the variable
+// is only a side-effect, it will never be used
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wunused-variable"
                 const bool registered_opl_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::opl,
diff --git a/include/osmium/io/detail/pbf.hpp b/include/osmium/io/detail/pbf.hpp
index f2975c5..15e457a 100644
--- a/include/osmium/io/detail/pbf.hpp
+++ b/include/osmium/io/detail/pbf.hpp
@@ -33,7 +33,7 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <stdexcept>
+#include <string>
 
 // needed for htonl and ntohl
 #ifndef _WIN32
@@ -43,6 +43,7 @@ DEALINGS IN THE SOFTWARE.
 #endif
 
 #include <osmium/io/error.hpp>
+#include <osmium/osm/location.hpp>
 
 namespace osmium {
 
@@ -62,6 +63,26 @@ namespace osmium {
 
     }; // struct pbf_error
 
+    namespace io {
+
+        namespace detail {
+
+            // the maximum size of a blob header in bytes
+            const int max_blob_header_size = 64 * 1024; // 64 kB
+
+            // the maximum size of an uncompressed blob in bytes
+            const uint64_t max_uncompressed_blob_size = 32 * 1024 * 1024; // 32 MB
+
+            // resolution for longitude/latitude used for conversion
+            // between representation as double and as int
+            const int64_t lonlat_resolution = 1000 * 1000 * 1000;
+
+            const int64_t resolution_convert = lonlat_resolution / osmium::Location::coordinate_precision;
+
+        }
+
+    }
+
 } // namespace osmium
 
 #endif // OSMIUM_IO_DETAIL_PBF_HPP
diff --git a/include/osmium/io/detail/pbf_decoder.hpp b/include/osmium/io/detail/pbf_decoder.hpp
new file mode 100644
index 0000000..79e899f
--- /dev/null
+++ b/include/osmium/io/detail/pbf_decoder.hpp
@@ -0,0 +1,760 @@
+#ifndef OSMIUM_IO_DETAIL_PBF_DECODER_HPP
+#define OSMIUM_IO_DETAIL_PBF_DECODER_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <algorithm>
+#include <iterator>
+#include <limits>
+
+#include <protozero/pbf_message.hpp>
+
+#include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
+#include <osmium/io/detail/protobuf_tags.hpp>
+#include <osmium/io/detail/zlib.hpp>
+#include <osmium/io/header.hpp>
+#include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
+#include <osmium/osm/types.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm/entity_bits.hpp>
+#include <osmium/util/cast.hpp>
+#include <osmium/util/delta.hpp>
+
+namespace osmium {
+
+    namespace io {
+
+        namespace detail {
+
+            using ptr_len_type = std::pair<const char*, size_t>;
+
+            class PBFPrimitiveBlockDecoder {
+
+                static constexpr size_t initial_buffer_size = 2 * 1024 * 1024;
+
+                ptr_len_type m_data;
+                std::vector<ptr_len_type> m_stringtable;
+
+                int64_t m_lon_offset = 0;
+                int64_t m_lat_offset = 0;
+                int64_t m_date_factor = 1000;
+                int32_t m_granularity = 100;
+
+                osmium::osm_entity_bits::type m_read_types;
+
+                osmium::memory::Buffer m_buffer { initial_buffer_size };
+
+                void decode_stringtable(const ptr_len_type& data) {
+                    if (!m_stringtable.empty()) {
+                        throw osmium::pbf_error("more than one stringtable in pbf file");
+                    }
+
+                    protozero::pbf_message<OSMFormat::StringTable> pbf_string_table(data);
+                    while (pbf_string_table.next(OSMFormat::StringTable::repeated_bytes_s)) {
+                        m_stringtable.push_back(pbf_string_table.get_data());
+                    }
+                }
+
+                void decode_primitive_block_metadata() {
+                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block(m_data);
+                    while (pbf_primitive_block.next()) {
+                        switch (pbf_primitive_block.tag()) {
+                            case OSMFormat::PrimitiveBlock::required_StringTable_stringtable:
+                                decode_stringtable(pbf_primitive_block.get_data());
+                                break;
+                            case OSMFormat::PrimitiveBlock::optional_int32_granularity:
+                                m_granularity = pbf_primitive_block.get_int32();
+                                break;
+                            case OSMFormat::PrimitiveBlock::optional_int32_date_granularity:
+                                m_date_factor = pbf_primitive_block.get_int32();
+                                break;
+                            case OSMFormat::PrimitiveBlock::optional_int64_lat_offset:
+                                m_lat_offset = pbf_primitive_block.get_int64();
+                                break;
+                            case OSMFormat::PrimitiveBlock::optional_int64_lon_offset:
+                                m_lon_offset = pbf_primitive_block.get_int64();
+                                break;
+                            default:
+                                pbf_primitive_block.skip();
+                        }
+                    }
+                }
+
+                void decode_primitive_block_data() {
+                    protozero::pbf_message<OSMFormat::PrimitiveBlock> pbf_primitive_block(m_data);
+                    while (pbf_primitive_block.next(OSMFormat::PrimitiveBlock::repeated_PrimitiveGroup_primitivegroup)) {
+                        protozero::pbf_message<OSMFormat::PrimitiveGroup> pbf_primitive_group = pbf_primitive_block.get_message();
+                        while (pbf_primitive_group.next()) {
+                            switch (pbf_primitive_group.tag()) {
+                                case OSMFormat::PrimitiveGroup::repeated_Node_nodes:
+                                    if (m_read_types & osmium::osm_entity_bits::node) {
+                                        decode_node(pbf_primitive_group.get_data());
+                                    } else {
+                                        pbf_primitive_group.skip();
+                                    }
+                                    break;
+                                case OSMFormat::PrimitiveGroup::optional_DenseNodes_dense:
+                                    if (m_read_types & osmium::osm_entity_bits::node) {
+                                        decode_dense_nodes(pbf_primitive_group.get_data());
+                                    } else {
+                                        pbf_primitive_group.skip();
+                                    }
+                                    break;
+                                case OSMFormat::PrimitiveGroup::repeated_Way_ways:
+                                    if (m_read_types & osmium::osm_entity_bits::way) {
+                                        decode_way(pbf_primitive_group.get_data());
+                                    } else {
+                                        pbf_primitive_group.skip();
+                                    }
+                                    break;
+                                case OSMFormat::PrimitiveGroup::repeated_Relation_relations:
+                                    if (m_read_types & osmium::osm_entity_bits::relation) {
+                                        decode_relation(pbf_primitive_group.get_data());
+                                    } else {
+                                        pbf_primitive_group.skip();
+                                    }
+                                    break;
+                                default:
+                                    pbf_primitive_group.skip();
+                            }
+                        }
+                    }
+                }
+
+                ptr_len_type decode_info(const ptr_len_type& data, osmium::OSMObject& object) {
+                    ptr_len_type user = std::make_pair("", 0);
+
+                    protozero::pbf_message<OSMFormat::Info> pbf_info(data);
+                    while (pbf_info.next()) {
+                        switch (pbf_info.tag()) {
+                            case OSMFormat::Info::optional_int32_version:
+                                {
+                                    auto version = pbf_info.get_int32();
+                                    if (version < 0) {
+                                        throw osmium::pbf_error("object version must not be negative");
+                                    }
+                                    object.set_version(static_cast_with_assert<object_version_type>(version));
+                                }
+                                break;
+                            case OSMFormat::Info::optional_int64_timestamp:
+                                object.set_timestamp(pbf_info.get_int64() * m_date_factor / 1000);
+                                break;
+                            case OSMFormat::Info::optional_int64_changeset:
+                                {
+                                    auto changeset_id = pbf_info.get_int64();
+                                    if (changeset_id < 0) {
+                                        throw osmium::pbf_error("object changeset_id must not be negative");
+                                    }
+                                    object.set_changeset(static_cast_with_assert<changeset_id_type>(changeset_id));
+                                }
+                                break;
+                            case OSMFormat::Info::optional_int32_uid:
+                                object.set_uid_from_signed(pbf_info.get_int32());
+                                break;
+                            case OSMFormat::Info::optional_uint32_user_sid:
+                                user = m_stringtable.at(pbf_info.get_uint32());
+                                break;
+                            case OSMFormat::Info::optional_bool_visible:
+                                object.set_visible(pbf_info.get_bool());
+                                break;
+                            default:
+                                pbf_info.skip();
+                        }
+                    }
+
+                    return user;
+                }
+
+                using kv_type = std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator>;
+
+                void build_tag_list(osmium::builder::Builder& builder, const kv_type& keys, const kv_type& vals) {
+                    if (keys.first != keys.second) {
+                        osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
+                        auto kit = keys.first;
+                        auto vit = vals.first;
+                        while (kit != keys.second) {
+                            if (vit == vals.second) {
+                                // this is against the spec, must have same number of elements
+                                throw osmium::pbf_error("PBF format error");
+                            }
+                            const auto& k = m_stringtable.at(*kit++);
+                            const auto& v = m_stringtable.at(*vit++);
+                            tl_builder.add_tag(k.first, k.second, v.first, v.second);
+                        }
+                    }
+                }
+
+                int32_t convert_pbf_coordinate(int64_t c) const {
+                    return (c * m_granularity + m_lon_offset) / resolution_convert;
+                }
+
+                void decode_node(const ptr_len_type& data) {
+                    osmium::builder::NodeBuilder builder(m_buffer);
+                    osmium::Node& node = builder.object();
+
+                    kv_type keys;
+                    kv_type vals;
+                    int64_t lon = std::numeric_limits<int64_t>::max();
+                    int64_t lat = std::numeric_limits<int64_t>::max();
+
+                    ptr_len_type user = { "", 0 };
+
+                    protozero::pbf_message<OSMFormat::Node> pbf_node(data);
+                    while (pbf_node.next()) {
+                        switch (pbf_node.tag()) {
+                            case OSMFormat::Node::required_sint64_id:
+                                node.set_id(pbf_node.get_sint64());
+                                break;
+                            case OSMFormat::Node::packed_uint32_keys:
+                                keys = pbf_node.get_packed_uint32();
+                                break;
+                            case OSMFormat::Node::packed_uint32_vals:
+                                vals = pbf_node.get_packed_uint32();
+                                break;
+                            case OSMFormat::Node::optional_Info_info:
+                                user = decode_info(pbf_node.get_data(), builder.object());
+                                break;
+                            case OSMFormat::Node::required_sint64_lat:
+                                lat = pbf_node.get_sint64();
+                                break;
+                            case OSMFormat::Node::required_sint64_lon:
+                                lon = pbf_node.get_sint64();
+                                break;
+                            default:
+                                pbf_node.skip();
+                        }
+                    }
+
+                    if (node.visible()) {
+                        if (lon == std::numeric_limits<int64_t>::max() ||
+                            lat == std::numeric_limits<int64_t>::max()) {
+                            throw osmium::pbf_error("illegal coordinate format");
+                        }
+                        node.set_location(osmium::Location(
+                                convert_pbf_coordinate(lon),
+                                convert_pbf_coordinate(lat)
+                        ));
+                    }
+
+                    builder.add_user(user.first, user.second);
+
+                    build_tag_list(builder, keys, vals);
+
+                    m_buffer.commit();
+                }
+
+                void decode_way(const ptr_len_type& data) {
+                    osmium::builder::WayBuilder builder(m_buffer);
+
+                    kv_type keys;
+                    kv_type vals;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> refs;
+
+                    ptr_len_type user = { "", 0 };
+
+                    protozero::pbf_message<OSMFormat::Way> pbf_way(data);
+                    while (pbf_way.next()) {
+                        switch (pbf_way.tag()) {
+                            case OSMFormat::Way::required_int64_id:
+                                builder.object().set_id(pbf_way.get_int64());
+                                break;
+                            case OSMFormat::Way::packed_uint32_keys:
+                                keys = pbf_way.get_packed_uint32();
+                                break;
+                            case OSMFormat::Way::packed_uint32_vals:
+                                vals = pbf_way.get_packed_uint32();
+                                break;
+                            case OSMFormat::Way::optional_Info_info:
+                                user = decode_info(pbf_way.get_data(), builder.object());
+                                break;
+                            case OSMFormat::Way::packed_sint64_refs:
+                                refs = pbf_way.get_packed_sint64();
+                                break;
+                            default:
+                                pbf_way.skip();
+                        }
+                    }
+
+                    builder.add_user(user.first, user.second);
+
+                    if (refs.first != refs.second) {
+                        osmium::builder::WayNodeListBuilder wnl_builder(m_buffer, &builder);
+                        osmium::util::DeltaDecode<int64_t> ref;
+                        while (refs.first != refs.second) {
+                            wnl_builder.add_node_ref(ref.update(*refs.first++));
+                        }
+                    }
+
+                    build_tag_list(builder, keys, vals);
+
+                    m_buffer.commit();
+                }
+
+                void decode_relation(const ptr_len_type& data) {
+                    osmium::builder::RelationBuilder builder(m_buffer);
+
+                    kv_type keys;
+                    kv_type vals;
+                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator> roles;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> refs;
+                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator> types;
+
+                    ptr_len_type user = { "", 0 };
+
+                    protozero::pbf_message<OSMFormat::Relation> pbf_relation(data);
+                    while (pbf_relation.next()) {
+                        switch (pbf_relation.tag()) {
+                            case OSMFormat::Relation::required_int64_id:
+                                builder.object().set_id(pbf_relation.get_int64());
+                                break;
+                            case OSMFormat::Relation::packed_uint32_keys:
+                                keys = pbf_relation.get_packed_uint32();
+                                break;
+                            case OSMFormat::Relation::packed_uint32_vals:
+                                vals = pbf_relation.get_packed_uint32();
+                                break;
+                            case OSMFormat::Relation::optional_Info_info:
+                                user = decode_info(pbf_relation.get_data(), builder.object());
+                                break;
+                            case OSMFormat::Relation::packed_int32_roles_sid:
+                                roles = pbf_relation.get_packed_int32();
+                                break;
+                            case OSMFormat::Relation::packed_sint64_memids:
+                                refs = pbf_relation.get_packed_sint64();
+                                break;
+                            case OSMFormat::Relation::packed_MemberType_types:
+                                types = pbf_relation.get_packed_enum();
+                                break;
+                            default:
+                                pbf_relation.skip();
+                        }
+                    }
+
+                    builder.add_user(user.first, user.second);
+
+                    if (refs.first != refs.second) {
+                        osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder);
+                        osmium::util::DeltaDecode<int64_t> ref;
+                        while (roles.first != roles.second && refs.first != refs.second && types.first != types.second) {
+                            const auto& r = m_stringtable.at(*roles.first++);
+                            int type = *types.first++;
+                            if (type < 0 || type > 2) {
+                                throw osmium::pbf_error("unknown relation member type");
+                            }
+                            rml_builder.add_member(
+                                osmium::item_type(type + 1),
+                                ref.update(*refs.first++),
+                                r.first,
+                                r.second
+                            );
+                        }
+                    }
+
+                    build_tag_list(builder, keys, vals);
+
+                    m_buffer.commit();
+                }
+
+                void decode_dense_nodes(const ptr_len_type& data) {
+                    bool has_info     = false;
+                    bool has_visibles = false;
+
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> ids;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lats;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> lons;
+
+                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  tags;
+
+                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  versions;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> timestamps;
+                    std::pair<protozero::pbf_reader::const_sint64_iterator, protozero::pbf_reader::const_sint64_iterator> changesets;
+                    std::pair<protozero::pbf_reader::const_sint32_iterator, protozero::pbf_reader::const_sint32_iterator> uids;
+                    std::pair<protozero::pbf_reader::const_sint32_iterator, protozero::pbf_reader::const_sint32_iterator> user_sids;
+                    std::pair<protozero::pbf_reader::const_int32_iterator,  protozero::pbf_reader::const_int32_iterator>  visibles;
+
+                    protozero::pbf_message<OSMFormat::DenseNodes> pbf_dense_nodes(data);
+                    while (pbf_dense_nodes.next()) {
+                        switch (pbf_dense_nodes.tag()) {
+                            case OSMFormat::DenseNodes::packed_sint64_id:
+                                ids = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::optional_DenseInfo_denseinfo:
+                                {
+                                    has_info = true;
+                                    protozero::pbf_message<OSMFormat::DenseInfo> pbf_dense_info = pbf_dense_nodes.get_message();
+                                    while (pbf_dense_info.next()) {
+                                        switch (pbf_dense_info.tag()) {
+                                            case OSMFormat::DenseInfo::packed_int32_version:
+                                                versions = pbf_dense_info.get_packed_int32();
+                                                break;
+                                            case OSMFormat::DenseInfo::packed_sint64_timestamp:
+                                                timestamps = pbf_dense_info.get_packed_sint64();
+                                                break;
+                                            case OSMFormat::DenseInfo::packed_sint64_changeset:
+                                                changesets = pbf_dense_info.get_packed_sint64();
+                                                break;
+                                            case OSMFormat::DenseInfo::packed_sint32_uid:
+                                                uids = pbf_dense_info.get_packed_sint32();
+                                                break;
+                                            case OSMFormat::DenseInfo::packed_sint32_user_sid:
+                                                user_sids = pbf_dense_info.get_packed_sint32();
+                                                break;
+                                            case OSMFormat::DenseInfo::packed_bool_visible:
+                                                has_visibles = true;
+                                                visibles = pbf_dense_info.get_packed_bool();
+                                                break;
+                                            default:
+                                                pbf_dense_info.skip();
+                                        }
+                                    }
+                                }
+                                break;
+                            case OSMFormat::DenseNodes::packed_sint64_lat:
+                                lats = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::packed_sint64_lon:
+                                lons = pbf_dense_nodes.get_packed_sint64();
+                                break;
+                            case OSMFormat::DenseNodes::packed_int32_keys_vals:
+                                tags = pbf_dense_nodes.get_packed_int32();
+                                break;
+                            default:
+                                pbf_dense_nodes.skip();
+                        }
+                    }
+
+                    osmium::util::DeltaDecode<int64_t> dense_id;
+                    osmium::util::DeltaDecode<int64_t> dense_latitude;
+                    osmium::util::DeltaDecode<int64_t> dense_longitude;
+                    osmium::util::DeltaDecode<int64_t> dense_uid;
+                    osmium::util::DeltaDecode<int64_t> dense_user_sid;
+                    osmium::util::DeltaDecode<int64_t> dense_changeset;
+                    osmium::util::DeltaDecode<int64_t> dense_timestamp;
+
+                    auto tag_it = tags.first;
+
+                    while (ids.first != ids.second) {
+                        if (lons.first == lons.second ||
+                            lats.first == lats.second) {
+                            // this is against the spec, must have same number of elements
+                            throw osmium::pbf_error("PBF format error");
+                        }
+
+                        bool visible = true;
+
+                        osmium::builder::NodeBuilder builder(m_buffer);
+                        osmium::Node& node = builder.object();
+
+                        node.set_id(dense_id.update(*ids.first++));
+
+                        if (has_info) {
+                            if (versions.first == versions.second ||
+                                changesets.first == changesets.second ||
+                                timestamps.first == timestamps.second ||
+                                uids.first == uids.second ||
+                                user_sids.first == user_sids.second) {
+                                // this is against the spec, must have same number of elements
+                                throw osmium::pbf_error("PBF format error");
+                            }
+
+                            auto version = *versions.first++;
+                            if (version < 0) {
+                                throw osmium::pbf_error("object version must not be negative");
+                            }
+                            node.set_version(static_cast<osmium::object_version_type>(version));
+
+                            auto changeset_id = dense_changeset.update(*changesets.first++);
+                            if (changeset_id < 0) {
+                                throw osmium::pbf_error("object changeset_id must not be negative");
+                            }
+                            node.set_changeset(static_cast<osmium::changeset_id_type>(changeset_id));
+
+                            node.set_timestamp(dense_timestamp.update(*timestamps.first++) * m_date_factor / 1000);
+                            node.set_uid_from_signed(static_cast<osmium::signed_user_id_type>(dense_uid.update(*uids.first++)));
+
+                            if (has_visibles) {
+                                if (visibles.first == visibles.second) {
+                                    // this is against the spec, must have same number of elements
+                                    throw osmium::pbf_error("PBF format error");
+                                }
+                                visible = *visibles.first++;
+                            }
+                            node.set_visible(visible);
+
+                            const auto& u = m_stringtable.at(dense_user_sid.update(*user_sids.first++));
+                            builder.add_user(u.first, u.second);
+                        } else {
+                            builder.add_user("");
+                        }
+
+                        if (visible) {
+                            builder.object().set_location(osmium::Location(
+                                    convert_pbf_coordinate(dense_longitude.update(*lons.first++)),
+                                    convert_pbf_coordinate(dense_latitude.update(*lats.first++))
+                            ));
+                        }
+
+                        if (tag_it != tags.second) {
+                            osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
+                            while (tag_it != tags.second && *tag_it != 0) {
+                                const auto& k = m_stringtable.at(*tag_it++);
+                                if (tag_it == tags.second) {
+                                    throw osmium::pbf_error("PBF format error"); // this is against the spec, keys/vals must come in pairs
+                                }
+                                const auto& v = m_stringtable.at(*tag_it++);
+                                tl_builder.add_tag(k.first, k.second, v.first, v.second);
+                            }
+
+                            if (tag_it != tags.second) {
+                                ++tag_it;
+                            }
+                        }
+
+                        m_buffer.commit();
+                    }
+
+                }
+
+            public:
+
+                explicit PBFPrimitiveBlockDecoder(const ptr_len_type& data, osmium::osm_entity_bits::type read_types) :
+                    m_data(data),
+                    m_read_types(read_types) {
+                }
+
+                PBFPrimitiveBlockDecoder(const PBFPrimitiveBlockDecoder&) = delete;
+                PBFPrimitiveBlockDecoder& operator=(const PBFPrimitiveBlockDecoder&) = delete;
+
+                PBFPrimitiveBlockDecoder(PBFPrimitiveBlockDecoder&&) = delete;
+                PBFPrimitiveBlockDecoder& operator=(PBFPrimitiveBlockDecoder&&) = delete;
+
+                ~PBFPrimitiveBlockDecoder() = default;
+
+                osmium::memory::Buffer operator()() {
+                    try {
+                        decode_primitive_block_metadata();
+                        decode_primitive_block_data();
+                    } catch (std::out_of_range&) {
+                        throw osmium::pbf_error("string id out of range");
+                    }
+
+                    return std::move(m_buffer);
+                }
+
+            }; // class PBFPrimitiveBlockDecoder
+
+            inline ptr_len_type decode_blob(const std::string& blob_data, std::string& output) {
+                int32_t raw_size;
+                std::pair<const char*, protozero::pbf_length_type> zlib_data;
+
+                protozero::pbf_message<FileFormat::Blob> pbf_blob(blob_data);
+                while (pbf_blob.next()) {
+                    switch (pbf_blob.tag()) {
+                        case FileFormat::Blob::optional_bytes_raw:
+                            {
+                                auto data_len = pbf_blob.get_data();
+                                if (data_len.second > max_uncompressed_blob_size) {
+                                    throw osmium::pbf_error("illegal blob size");
+                                }
+                                return data_len;
+                            }
+                        case FileFormat::Blob::optional_int32_raw_size:
+                            raw_size = pbf_blob.get_int32();
+                            if (raw_size <= 0 || uint32_t(raw_size) > max_uncompressed_blob_size) {
+                                throw osmium::pbf_error("illegal blob size");
+                            }
+                            break;
+                        case FileFormat::Blob::optional_bytes_zlib_data:
+                            zlib_data = pbf_blob.get_data();
+                            break;
+                        case FileFormat::Blob::optional_bytes_lzma_data:
+                            throw osmium::pbf_error("lzma blobs not implemented");
+                        default:
+                            throw osmium::pbf_error("unknown compression");
+                    }
+                }
+
+                if (zlib_data.second != 0) {
+                    return osmium::io::detail::zlib_uncompress_string(
+                        zlib_data.first,
+                        static_cast<unsigned long>(zlib_data.second),
+                        static_cast<unsigned long>(raw_size),
+                        output
+                    );
+                }
+
+                throw osmium::pbf_error("blob contains no data");
+            }
+
+            inline osmium::Box decode_header_bbox(const ptr_len_type& data) {
+                    int64_t left   = std::numeric_limits<int64_t>::max();
+                    int64_t right  = std::numeric_limits<int64_t>::max();
+                    int64_t top    = std::numeric_limits<int64_t>::max();
+                    int64_t bottom = std::numeric_limits<int64_t>::max();
+
+                    protozero::pbf_message<OSMFormat::HeaderBBox> pbf_header_bbox(data);
+                    while (pbf_header_bbox.next()) {
+                        switch (pbf_header_bbox.tag()) {
+                            case OSMFormat::HeaderBBox::required_sint64_left:
+                                left = pbf_header_bbox.get_sint64();
+                                break;
+                            case OSMFormat::HeaderBBox::required_sint64_right:
+                                right = pbf_header_bbox.get_sint64();
+                                break;
+                            case OSMFormat::HeaderBBox::required_sint64_top:
+                                top = pbf_header_bbox.get_sint64();
+                                break;
+                            case OSMFormat::HeaderBBox::required_sint64_bottom:
+                                bottom = pbf_header_bbox.get_sint64();
+                                break;
+                            default:
+                                pbf_header_bbox.skip();
+                        }
+                    }
+
+                    if (left   == std::numeric_limits<int64_t>::max() ||
+                        right  == std::numeric_limits<int64_t>::max() ||
+                        top    == std::numeric_limits<int64_t>::max() ||
+                        bottom == std::numeric_limits<int64_t>::max()) {
+                        throw osmium::pbf_error("invalid bbox");
+                    }
+
+                    osmium::Box box;
+                    box.extend(osmium::Location(left  / resolution_convert, bottom / resolution_convert));
+                    box.extend(osmium::Location(right / resolution_convert, top    / resolution_convert));
+
+                    return box;
+            }
+
+            inline osmium::io::Header decode_header_block(const ptr_len_type& data) {
+                osmium::io::Header header;
+                int i = 0;
+
+                protozero::pbf_message<OSMFormat::HeaderBlock> pbf_header_block(data);
+                while (pbf_header_block.next()) {
+                    switch (pbf_header_block.tag()) {
+                        case OSMFormat::HeaderBlock::optional_HeaderBBox_bbox:
+                            header.add_box(decode_header_bbox(pbf_header_block.get_data()));
+                            break;
+                        case OSMFormat::HeaderBlock::repeated_string_required_features:
+                            {
+                                auto feature = pbf_header_block.get_data();
+                                if (!strncmp("OsmSchema-V0.6", feature.first, feature.second)) {
+                                    // intentionally left blank
+                                } else if (!strncmp("DenseNodes", feature.first, feature.second)) {
+                                    header.set("pbf_dense_nodes", true);
+                                } else if (!strncmp("HistoricalInformation", feature.first, feature.second)) {
+                                    header.set_has_multiple_object_versions(true);
+                                } else {
+                                    std::string msg("required feature not supported: ");
+                                    msg.append(feature.first, feature.second);
+                                    throw osmium::pbf_error(msg);
+                                }
+                            }
+                            break;
+                        case OSMFormat::HeaderBlock::repeated_string_optional_features:
+                            header.set("pbf_optional_feature_" + std::to_string(i++), pbf_header_block.get_string());
+                            break;
+                        case OSMFormat::HeaderBlock::optional_string_writingprogram:
+                            header.set("generator", pbf_header_block.get_string());
+                            break;
+                        case OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp:
+                            header.set("osmosis_replication_timestamp", osmium::Timestamp(pbf_header_block.get_int64()).to_iso());
+                            break;
+                        case OSMFormat::HeaderBlock::optional_int64_osmosis_replication_sequence_number:
+                            header.set("osmosis_replication_sequence_number", std::to_string(pbf_header_block.get_int64()));
+                            break;
+                        case OSMFormat::HeaderBlock::optional_string_osmosis_replication_base_url:
+                            header.set("osmosis_replication_base_url", pbf_header_block.get_string());
+                            break;
+                        default:
+                            pbf_header_block.skip();
+                    }
+                }
+
+                return header;
+            }
+
+            /**
+             * Decode HeaderBlock.
+             *
+             * @param header_block_data Input data
+             * @returns Header object
+             * @throws osmium::pbf_error If there was a parsing error
+             */
+            inline osmium::io::Header decode_header(const std::string& header_block_data) {
+                std::string output;
+
+                return decode_header_block(decode_blob(header_block_data, output));
+            }
+
+            class PBFDataBlobDecoder {
+
+                std::shared_ptr<std::string> m_input_buffer;
+                osmium::osm_entity_bits::type m_read_types;
+
+            public:
+
+                PBFDataBlobDecoder(std::string&& input_buffer, osmium::osm_entity_bits::type read_types) :
+                    m_input_buffer(std::make_shared<std::string>(std::move(input_buffer))),
+                    m_read_types(read_types) {
+                }
+
+                PBFDataBlobDecoder(const PBFDataBlobDecoder&) = default;
+                PBFDataBlobDecoder& operator=(const PBFDataBlobDecoder&) = default;
+
+                PBFDataBlobDecoder(PBFDataBlobDecoder&&) = default;
+                PBFDataBlobDecoder& operator=(PBFDataBlobDecoder&&) = default;
+
+                ~PBFDataBlobDecoder() = default;
+
+                osmium::memory::Buffer operator()() {
+                    std::string output;
+                    PBFPrimitiveBlockDecoder decoder(decode_blob(*m_input_buffer, output), m_read_types);
+                    return decoder();
+                }
+
+            }; // class PBFDataBlobDecoder
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+#endif // OSMIUM_IO_DETAIL_PBF_DECODER_HPP
diff --git a/include/osmium/io/detail/pbf_input_format.hpp b/include/osmium/io/detail/pbf_input_format.hpp
index 93ba6ca..7817d27 100644
--- a/include/osmium/io/detail/pbf_input_format.hpp
+++ b/include/osmium/io/detail/pbf_input_format.hpp
@@ -49,10 +49,12 @@ DEALINGS IN THE SOFTWARE.
 #include <thread>
 #include <type_traits>
 
+#include <protozero/pbf_message.hpp>
+
 #include <osmium/io/detail/input_format.hpp>
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_type_conv.hpp>
-#include <osmium/io/detail/pbf_parser.hpp>
+#include <osmium/io/detail/pbf_decoder.hpp>
+#include <osmium/io/detail/protobuf_tags.hpp>
 #include <osmium/io/error.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
@@ -77,13 +79,13 @@ namespace osmium {
 
         namespace detail {
 
-            typedef osmium::thread::Queue<std::future<osmium::memory::Buffer>> queue_type;
-
             /**
              * Class for parsing PBF files.
              */
             class PBFInputFormat : public osmium::io::detail::InputFormat {
 
+                typedef osmium::thread::Queue<std::future<osmium::memory::Buffer>> queue_type;
+
                 bool m_use_thread_pool;
                 bool m_eof { false };
                 queue_type m_queue;
@@ -116,15 +118,10 @@ namespace osmium {
                 }
 
                 /**
-                 * Read BlobHeader by first reading the size and then the
-                 * BlobHeader. The BlobHeader contains a type field (which is
-                 * checked against the expected type) and a size field.
-                 *
-                 * @param expected_type Expected type of data ("OSMHeader" or
-                 *                      "OSMData").
-                 * @returns Size of the data read from BlobHeader (0 on EOF).
+                 * Read 4 bytes in network byte order from file. They contain
+                 * the length of the following BlobHeader.
                  */
-                size_t read_blob_header(const char* expected_type) {
+                uint32_t read_blob_header_size_from_file() {
                     uint32_t size_in_network_byte_order;
 
                     try {
@@ -134,37 +131,76 @@ namespace osmium {
                         return 0; // EOF
                     }
 
-                    uint32_t size = ntohl(size_in_network_byte_order);
-                    if (size > static_cast<uint32_t>(OSMPBF::max_blob_header_size)) {
+                    const uint32_t size = ntohl(size_in_network_byte_order);
+                    if (size > static_cast<uint32_t>(max_blob_header_size)) {
                         throw osmium::pbf_error("invalid BlobHeader size (> max_blob_header_size)");
                     }
 
-                    OSMPBF::BlobHeader blob_header;
-                    if (!blob_header.ParseFromString(read_from_input_queue(size))) {
-                        throw osmium::pbf_error("failed to parse BlobHeader");
+                    return size;
+                }
+
+                /**
+                 * Decode the BlobHeader. Make sure it contains the expected
+                 * type. Return the size of the following Blob.
+                 */
+                size_t decode_blob_header(protozero::pbf_message<FileFormat::BlobHeader>&& pbf_blob_header, const char* expected_type) {
+                    std::pair<const char*, size_t> blob_header_type;
+                    size_t blob_header_datasize = 0;
+
+                    while (pbf_blob_header.next()) {
+                        switch (pbf_blob_header.tag()) {
+                            case FileFormat::BlobHeader::required_string_type:
+                                blob_header_type = pbf_blob_header.get_data();
+                                break;
+                            case FileFormat::BlobHeader::required_int32_datasize:
+                                blob_header_datasize = pbf_blob_header.get_int32();
+                                break;
+                            default:
+                                pbf_blob_header.skip();
+                        }
                     }
 
-                    if (blob_header.type() != expected_type) {
+                    if (blob_header_datasize == 0) {
+                        throw osmium::pbf_error("PBF format error: BlobHeader.datasize missing or zero.");
+                    }
+
+                    if (strncmp(expected_type, blob_header_type.first, blob_header_type.second)) {
                         throw osmium::pbf_error("blob does not have expected type (OSMHeader in first blob, OSMData in following blobs)");
                     }
 
-                    return static_cast<size_t>(blob_header.datasize());
+                    return blob_header_datasize;
+                }
+
+                size_t check_type_and_get_blob_size(const char* expected_type) {
+                    assert(expected_type);
+
+                    auto size = read_blob_header_size_from_file();
+                    if (size == 0) { // EOF
+                        return 0;
+                    }
+
+                    std::string blob_header = read_from_input_queue(size);
+
+                    return decode_blob_header(protozero::pbf_message<FileFormat::BlobHeader>(blob_header), expected_type);
                 }
 
                 void parse_osm_data(osmium::osm_entity_bits::type read_types) {
                     osmium::thread::set_thread_name("_osmium_pbf_in");
-                    int n = 0;
-                    while (auto size = read_blob_header("OSMData")) {
+
+                    while (auto size = check_type_and_get_blob_size("OSMData")) {
+                        std::string input_buffer = read_from_input_queue(size);
+                        if (input_buffer.size() > max_uncompressed_blob_size) {
+                            throw osmium::pbf_error(std::string("invalid blob size: " + std::to_string(input_buffer.size())));
+                        }
 
                         if (m_use_thread_pool) {
-                            m_queue.push(osmium::thread::Pool::instance().submit(DataBlobParser{read_from_input_queue(size), read_types}));
+                            m_queue.push(osmium::thread::Pool::instance().submit(PBFDataBlobDecoder{ std::move(input_buffer), read_types }));
                         } else {
                             std::promise<osmium::memory::Buffer> promise;
                             m_queue.push(promise.get_future());
-                            DataBlobParser data_blob_parser{read_from_input_queue(size), read_types};
+                            PBFDataBlobDecoder data_blob_parser{ std::move(input_buffer), read_types };
                             promise.set_value(data_blob_parser());
                         }
-                        ++n;
 
                         if (m_quit_input_thread) {
                             return;
@@ -198,11 +234,10 @@ namespace osmium {
                     m_quit_input_thread(false),
                     m_input_queue(input_queue),
                     m_input_buffer() {
-                    GOOGLE_PROTOBUF_VERIFY_VERSION;
 
                     // handle OSMHeader
-                    auto size = read_blob_header("OSMHeader");
-                    m_header = parse_header_blob(read_from_input_queue(size));
+                    const auto size = check_type_and_get_blob_size("OSMHeader");
+                    m_header = decode_header(read_from_input_queue(size));
 
                     if (m_read_which_entities != osmium::osm_entity_bits::nothing) {
                         m_reader = std::thread(&PBFInputFormat::parse_osm_data, this, m_read_which_entities);
@@ -247,10 +282,15 @@ namespace osmium {
 
             namespace {
 
+// we want the register_input_format() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
                 const bool registered_pbf_input = osmium::io::detail::InputFormatFactory::instance().register_input_format(osmium::io::file_format::pbf,
                     [](const osmium::io::File& file, osmium::osm_entity_bits::type read_which_entities, osmium::thread::Queue<std::string>& input_queue) {
                         return new osmium::io::detail::PBFInputFormat(file, read_which_entities, input_queue);
                 });
+#pragma GCC diagnostic pop
 
             } // anonymous namespace
 
diff --git a/include/osmium/io/detail/pbf_output_format.hpp b/include/osmium/io/detail/pbf_output_format.hpp
index 7afd2ee..8d8a079 100644
--- a/include/osmium/io/detail/pbf_output_format.hpp
+++ b/include/osmium/io/detail/pbf_output_format.hpp
@@ -33,75 +33,13 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-/*
-
-About the .osm.pbf file format
-This is an excerpt of <http://wiki.openstreetmap.org/wiki/PBF_Format>
-
-The .osm.pbf format and it's derived formats (.osh.pbf and .osc.pbf) are encoded
-using googles protobuf library for the low-level storage. They are constructed
-by nesting data on two levels:
-
-On the lower level the file is constructed using BlobHeaders and Blobs. A .osm.pbf
-file contains multiple sequences of
- 1. a 4-byte header size, stored in network-byte-order
- 2. a BlobHeader of exactly this size
- 3. a Blob
-
-The BlobHeader tells the reader about the type and size of the following Blob. The
-Blob can contain data in raw or zlib-compressed form. After uncompressing the blob
-it is treated differently depending on the type specified in the BlobHeader.
-
-The contents of the Blob belongs to the higher level. It contains either an HeaderBlock
-(type="OSMHeader") or an PrimitiveBlock (type="OSMData"). The file needs to have
-at least one HeaderBlock before the first PrimitiveBlock.
-
-The HeaderBlock contains meta-information like the writing program or a bbox. It may
-also contain multiple "required features" that describe what kinds of input a
-reading program needs to handle in order to fully understand the files' contents.
-
-The PrimitiveBlock can store multiple types of objects (i.e. 5 nodes, 2 ways and
-1 relation). It contains one or more PrimitiveGroup which in turn contain multiple
-nodes, ways or relations. A PrimitiveGroup should only contain one kind of object.
-
-There's a special kind of "object type" called dense-nodes. It is used to store nodes
-in a very dense format, avoiding message overheads and using delta-encoding for nearly
-all ids.
-
-All Strings are stored as indexes to rows in a StringTable. The StringTable contains
-one row for each used string, so strings that are used multiple times need to be
-stored only once. The StringTable is sorted by usage-count, so the most often used
-string is stored at index 1.
-
-A simple outline of a .osm.pbf file could look like this:
-
-  4-bytes header size
-  BlobHeader
-  Blob
-    HeaderBlock
-  4-bytes header size
-  BlobHeader
-  Blob
-    PrimitiveBlock
-      StringTable
-      PrimitiveGroup
-        5 nodes
-      PrimitiveGroup
-        2 ways
-      PrimitiveGroup
-        1 relation
-
-More complete outlines of real .osm.pbf files can be created using the osmpbf-outline tool:
- <https://github.com/MaZderMind/OSM-binary/tree/osmpbf-outline>
-*/
-
 #include <algorithm>
 #include <chrono>
 #include <cmath>
 #include <cstdint>
 #include <cstdlib>
 #include <future>
-#include <iostream>
+#include <iterator>
 #include <memory>
 #include <ratio>
 #include <string>
@@ -109,11 +47,15 @@ More complete outlines of real .osm.pbf files can be created using the osmpbf-ou
 #include <time.h>
 #include <utility>
 
+#include <boost/iterator/transform_iterator.hpp>
+
+#include <protozero/pbf_builder.hpp>
+
 #include <osmium/handler.hpp>
 #include <osmium/io/detail/output_format.hpp>
 #include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_type_conv.hpp>
-#include <osmium/io/detail/pbf_stringtable.hpp>
+#include <osmium/io/detail/protobuf_tags.hpp>
+#include <osmium/io/detail/string_table.hpp>
 #include <osmium/io/detail/zlib.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/file_format.hpp>
@@ -139,787 +81,493 @@ namespace osmium {
 
         namespace detail {
 
-            namespace {
-
-                /**
-                 * Serialize a protobuf message into a Blob, optionally apply compression
-                 * and return it together with a BlobHeader ready to be written to a file.
-                 *
-                 * @param type Type-string used in the BlobHeader.
-                 * @param msg Protobuf-message.
-                 * @param use_compression Should the output be compressed using zlib?
-                 */
-                std::string serialize_blob(const std::string& type, const google::protobuf::MessageLite& msg, bool use_compression) {
-                    OSMPBF::Blob pbf_blob;
-
-                    {
-                        std::string content;
-                        msg.SerializeToString(&content);
-
-                        pbf_blob.set_raw_size(static_cast_with_assert<::google::protobuf::int32>(content.size()));
-
-                        if (use_compression) {
-                            pbf_blob.set_zlib_data(osmium::io::detail::zlib_compress(content));
-                        } else {
-                            pbf_blob.set_raw(content);
-                        }
-                    }
-
-                    std::string blob_data;
-                    pbf_blob.SerializeToString(&blob_data);
-
-                    OSMPBF::BlobHeader pbf_blob_header;
-                    pbf_blob_header.set_type(type);
-                    pbf_blob_header.set_datasize(static_cast_with_assert<::google::protobuf::int32>(blob_data.size()));
-
-                    std::string blob_header_data;
-                    pbf_blob_header.SerializeToString(&blob_header_data);
-
-                    uint32_t sz = htonl(static_cast_with_assert<uint32_t>(blob_header_data.size()));
-
-                    // write to output: the 4-byte BlobHeader-Size followed by the BlobHeader followed by the Blob
-                    std::string output;
-                    output.reserve(sizeof(sz) + blob_header_data.size() + blob_data.size());
-                    output.append(reinterpret_cast<const char*>(&sz), sizeof(sz));
-                    output.append(blob_header_data);
-                    output.append(blob_data);
-
-                    return output;
+            /**
+             * Maximum number of items in a primitive block.
+             *
+             * The uncompressed length of a Blob *should* be less
+             * than 16 megabytes and *must* be less than 32 megabytes.
+             *
+             * A block may contain any number of entities, as long as
+             * the size limits for the surrounding blob are obeyed.
+             * However, for simplicity, the current Osmosis (0.38)
+             * as well as Osmium implementation always
+             * uses at most 8k entities in a block.
+             */
+            constexpr int32_t max_entities_per_block = 8000;
+
+            constexpr int location_granularity = 100;
+
+            /**
+             * convert a double lat or lon value to an int, respecting the granularity
+             */
+            inline int64_t lonlat2int(double lonlat) {
+                return static_cast<int64_t>(std::round(lonlat * lonlat_resolution / location_granularity));
+            }
+
+            /**
+             * Serialize a protobuf message into a Blob, optionally apply compression
+             * and return it together with a BlobHeader ready to be written to a file.
+             *
+             * @param type Type-string used in the BlobHeader.
+             * @param msg Protobuf-message.
+             * @param use_compression Should the output be compressed using zlib?
+             */
+            inline std::string serialize_blob(const std::string& type, const std::string& msg, bool use_compression) {
+                std::string blob_data;
+                protozero::pbf_builder<FileFormat::Blob> pbf_blob(blob_data);
+
+                if (use_compression) {
+                    pbf_blob.add_int32(FileFormat::Blob::optional_int32_raw_size, msg.size());
+                    pbf_blob.add_bytes(FileFormat::Blob::optional_bytes_zlib_data, osmium::io::detail::zlib_compress(msg));
+                } else {
+                    pbf_blob.add_bytes(FileFormat::Blob::optional_bytes_raw, msg);
                 }
 
-            } // anonymous namespace
+                std::string blob_header_data;
+                protozero::pbf_builder<FileFormat::BlobHeader> pbf_blob_header(blob_header_data);
 
-            class PBFOutputFormat : public osmium::io::detail::OutputFormat, public osmium::handler::Handler {
+                pbf_blob_header.add_string(FileFormat::BlobHeader::required_string_type, type);
+                pbf_blob_header.add_int32(FileFormat::BlobHeader::required_int32_datasize, blob_data.size());
 
-                /**
-                 * Maximum number of items in a primitive block.
-                 *
-                 * The uncompressed length of a Blob *should* be less
-                 * than 16 megabytes and *must* be less than 32 megabytes.
-                 *
-                 * A block may contain any number of entities, as long as
-                 * the size limits for the surrounding blob are obeyed.
-                 * However, for simplicity, the current Osmosis (0.38)
-                 * as well as Osmium implementation always
-                 * uses at most 8k entities in a block.
-                 */
-                static constexpr uint32_t max_block_contents = 8000;
+                uint32_t sz = htonl(static_cast_with_assert<uint32_t>(blob_header_data.size()));
 
-                /**
-                 * The output buffer (block) will be filled to about
-                 * 95% and then written to disk. This leaves more than
-                 * enough space for the string table (which typically
-                 * needs about 0.1 to 0.3% of the block size).
-                 */
-                static constexpr int64_t buffer_fill_percent = 95;
+                // write to output: the 4-byte BlobHeader-Size followed by the BlobHeader followed by the Blob
+                std::string output;
+                output.reserve(sizeof(sz) + blob_header_data.size() + blob_data.size());
+                output.append(reinterpret_cast<const char*>(&sz), sizeof(sz));
+                output.append(blob_header_data);
+                output.append(blob_data);
 
-                /**
-                 * protobuf-struct of a HeaderBlock
-                 */
-                OSMPBF::HeaderBlock pbf_header_block;
+                return output;
+            }
 
-                /**
-                 * protobuf-struct of a PrimitiveBlock
-                 */
-                OSMPBF::PrimitiveBlock pbf_primitive_block;
+            class DenseNodes {
 
-                /**
-                 * pointer to PrimitiveGroups inside the current PrimitiveBlock,
-                 * used for writing nodes, ways or relations
-                 */
-                OSMPBF::PrimitiveGroup* pbf_nodes;
-                OSMPBF::PrimitiveGroup* pbf_ways;
-                OSMPBF::PrimitiveGroup* pbf_relations;
+                StringTable& m_stringtable;
 
-                /**
-                 * To flexibly handle multiple resolutions, the granularity, or
-                 * resolution used for representing locations is adjustable in
-                 * multiples of 1 nanodegree. The default scaling factor is 100
-                 * nanodegrees, corresponding to about ~1cm at the equator.
-                 * This is the current resolution of the OSM database.
-                 */
-                int m_location_granularity;
+                std::vector<int64_t> m_ids;
 
-                /**
-                 * The granularity used for representing timestamps is also adjustable in
-                 * multiples of 1 millisecond. The default scaling factor is 1000
-                 * milliseconds, which is the current resolution of the OSM database.
-                 */
-                int m_date_granularity;
+                std::vector<int32_t> m_versions;
+                std::vector<int64_t> m_timestamps;
+                std::vector<int64_t> m_changesets;
+                std::vector<int32_t> m_uids;
+                std::vector<int32_t> m_user_sids;
+                std::vector<bool> m_visibles;
 
-                /**
-                 * should nodes be serialized into the dense format?
-                 *
-                 * nodes can be encoded one of two ways, as a Node
-                 * (m_use_dense_nodes = false) and a special dense format.
-                 * In the dense format, all information is stored 'column wise',
-                 * as an array of ID's, array of latitudes, and array of
-                 * longitudes. Each column is delta-encoded. This reduces
-                 * header overheads and allows delta-coding to work very effectively.
-                 */
-                bool m_use_dense_nodes {true};
+                std::vector<int64_t> m_lats;
+                std::vector<int64_t> m_lons;
+                std::vector<int32_t> m_tags;
 
-                /**
-                 * should the PBF blobs contain zlib compressed data?
-                 *
-                 * the zlib compression is optional, it's possible to store the
-                 * blobs in raw format. Disabling the compression can improve the
-                 * writing speed a little but the output will be 2x to 3x bigger.
-                 */
-                bool m_use_compression {true};
+                osmium::util::DeltaEncode<int64_t> m_delta_id;
 
-                /**
-                 * Should the string tables in the data blocks be sorted?
-                 *
-                 * Not sorting the string tables makes writing PBF files
-                 * slightly faster.
-                 */
-                bool m_sort_stringtables { true };
+                osmium::util::DeltaEncode<int64_t> m_delta_timestamp;
+                osmium::util::DeltaEncode<int64_t> m_delta_changeset;
+                osmium::util::DeltaEncode<int32_t> m_delta_uid;
+                osmium::util::DeltaEncode<int32_t> m_delta_user_sid;
 
-                /**
-                 * While the .osm.pbf-format is able to carry all meta information, it is
-                 * also able to omit this information to reduce size.
-                 */
-                bool m_should_add_metadata {true};
+                osmium::util::DeltaEncode<int64_t> m_delta_lat;
+                osmium::util::DeltaEncode<int64_t> m_delta_lon;
 
-                /**
-                 * Should the visible flag be added on objects?
-                 */
+                bool m_add_metadata;
                 bool m_add_visible;
 
-                /**
-                 * counter used to quickly check the number of objects stored inside
-                 * the current PrimitiveBlock. When the counter reaches max_block_contents
-                 * the PrimitiveBlock is serialized into a Blob and flushed to the file.
-                 *
-                 * this check is performed in check_block_contents_counter() which is
-                 * called once for each object.
-                 */
-                uint16_t primitive_block_contents;
-                int primitive_block_size;
-
-                // StringTable management
-                StringTable string_table;
+            public:
 
-                /**
-                 * These variables are used to calculate the
-                 * delta-encoding while storing dense-nodes. It holds the last seen values
-                 * from which the difference is stored into the protobuf.
-                 */
-                osmium::util::DeltaEncode<int64_t> m_delta_id;
-                osmium::util::DeltaEncode<int64_t> m_delta_lat;
-                osmium::util::DeltaEncode<int64_t> m_delta_lon;
-                osmium::util::DeltaEncode<int64_t> m_delta_timestamp;
-                osmium::util::DeltaEncode<int64_t> m_delta_changeset;
-                osmium::util::DeltaEncode<int64_t> m_delta_uid;
-                osmium::util::DeltaEncode<::google::protobuf::int32> m_delta_user_sid;
+                DenseNodes(StringTable& stringtable, bool add_metadata, bool add_visible) :
+                    m_stringtable(stringtable),
+                    m_add_metadata(add_metadata),
+                    m_add_visible(add_visible) {
+                }
 
-                bool debug;
+                void clear() {
+                    m_ids.clear();
 
-                bool has_debug_level(int) {
-                    return false;
-                }
+                    m_versions.clear();
+                    m_timestamps.clear();
+                    m_changesets.clear();
+                    m_uids.clear();
+                    m_user_sids.clear();
+                    m_visibles.clear();
 
-                ///// Blob writing /////
+                    m_lats.clear();
+                    m_lons.clear();
+                    m_tags.clear();
 
-                void delta_encode_string_ids() {
-                    if (pbf_nodes && pbf_nodes->has_dense()) {
-                        OSMPBF::DenseNodes* dense = pbf_nodes->mutable_dense();
+                    m_delta_id.clear();
 
-                        if (dense->has_denseinfo()) {
-                            OSMPBF::DenseInfo* denseinfo = dense->mutable_denseinfo();
+                    m_delta_timestamp.clear();
+                    m_delta_changeset.clear();
+                    m_delta_uid.clear();
+                    m_delta_user_sid.clear();
 
-                            for (int i = 0, l=denseinfo->user_sid_size(); i<l; ++i) {
-                                auto user_sid = denseinfo->user_sid(i);
-                                denseinfo->set_user_sid(i, m_delta_user_sid.update(user_sid));
-                            }
-                        }
-                    }
+                    m_delta_lat.clear();
+                    m_delta_lon.clear();
                 }
 
-                /**
-                 * Before a PrimitiveBlock gets serialized, all interim StringTable-ids needs to be
-                 * mapped to the associated real StringTable ids. This is done in this function.
-                 *
-                 * This function needs to know about the concrete structure of all item types to find
-                 * all occurrences of string-ids.
-                 */
-                void map_string_ids() {
-                    // test, if the node-block has been allocated
-                    if (pbf_nodes) {
-                        // iterate over all nodes, passing them to the map_common_string_ids function
-                        for (int i = 0, l=pbf_nodes->nodes_size(); i<l; ++i) {
-                            map_common_string_ids(pbf_nodes->mutable_nodes(i));
-                        }
-
-                        // test, if the node-block has a densenodes structure
-                        if (pbf_nodes->has_dense()) {
-                            // get a pointer to the densenodes structure
-                            OSMPBF::DenseNodes* dense = pbf_nodes->mutable_dense();
-
-                            // in the densenodes structure keys and vals are encoded in an intermixed
-                            // array, individual nodes are seperated by a value of 0 (0 in the StringTable
-                            // is always unused). String-ids of 0 are thus kept alone.
-                            for (int i = 0, l=dense->keys_vals_size(); i<l; ++i) {
-                                // map interim string-ids > 0 to real string ids
-                                auto sid = dense->keys_vals(i);
-                                if (sid > 0) {
-                                    dense->set_keys_vals(i, string_table.map_string_id(sid));
-                                }
-                            }
-
-                            // test if the densenodes block has meta infos
-                            if (dense->has_denseinfo()) {
-                                // get a pointer to the denseinfo structure
-                                OSMPBF::DenseInfo* denseinfo = dense->mutable_denseinfo();
-
-                                // iterate over all username string-ids
-                                for (int i = 0, l=denseinfo->user_sid_size(); i<l; ++i) {
-                                    // map interim string-ids > 0 to real string ids
-                                    auto user_sid = string_table.map_string_id(denseinfo->user_sid(i));
-
-                                    // delta encode the string-id
-                                    denseinfo->set_user_sid(i, m_delta_user_sid.update(user_sid));
-                                }
-                            }
-                        }
-                    }
+                size_t size() const {
+                    return m_ids.size() * 3 * sizeof(int64_t);
+                }
 
-                    // test, if the ways-block has been allocated
-                    if (pbf_ways) {
-                        // iterate over all ways, passing them to the map_common_string_ids function
-                        for (int i = 0, l=pbf_ways->ways_size(); i<l; ++i) {
-                            map_common_string_ids(pbf_ways->mutable_ways(i));
-                        }
-                    }
+                void add_node(const osmium::Node& node) {
+                    m_ids.push_back(m_delta_id.update(node.id()));
 
-                    // test, if the relations-block has been allocated
-                    if (pbf_relations) {
-                        // iterate over all relations
-                        for (int i = 0, l=pbf_relations->relations_size(); i<l; ++i) {
-                            // get a pointer to the relation
-                            OSMPBF::Relation* relation = pbf_relations->mutable_relations(i);
-
-                            // pass them to the map_common_string_ids function
-                            map_common_string_ids(relation);
-
-                            // iterate over all relation members, mapping the interim string-ids
-                            // of the role to real string ids
-                            for (int mi = 0; mi < relation->roles_sid_size(); ++mi) {
-                                relation->set_roles_sid(mi, string_table.map_string_id(relation->roles_sid(mi)));
-                            }
+                    if (m_add_metadata) {
+                        m_versions.push_back(node.version());
+                        m_timestamps.push_back(m_delta_timestamp.update(node.timestamp()));
+                        m_changesets.push_back(m_delta_changeset.update(node.changeset()));
+                        m_uids.push_back(m_delta_uid.update(node.uid()));
+                        m_user_sids.push_back(m_delta_user_sid.update(m_stringtable.add(node.user())));
+                        if (m_add_visible) {
+                            m_visibles.push_back(node.visible());
                         }
                     }
-                }
 
-                /**
-                * a helper function used in map_string_ids to map common interim string-ids of the
-                * user name and all tags to real string ids.
-                *
-                * TPBFObject is either OSMPBF::Node, OSMPBF::Way or OSMPBF::Relation.
-                */
-                template <class TPBFObject>
-                void map_common_string_ids(TPBFObject* in) {
-                    // if the object has meta-info attached
-                    if (in->has_info()) {
-                        // map the interim-id of the user name to a real id
-                        OSMPBF::Info* info = in->mutable_info();
-                        info->set_user_sid(string_table.map_string_id(info->user_sid()));
-                    }
+                    m_lats.push_back(m_delta_lat.update(lonlat2int(node.location().lat_without_check())));
+                    m_lons.push_back(m_delta_lon.update(lonlat2int(node.location().lon_without_check())));
 
-                    // iterate over all tags and map the interim-ids of the key and the value to real ids
-                    for (int i = 0, l=in->keys_size(); i<l; ++i) {
-                        in->set_keys(i, string_table.map_string_id(in->keys(i)));
-                        in->set_vals(i, string_table.map_string_id(in->vals(i)));
+                    for (const auto& tag : node.tags()) {
+                        m_tags.push_back(m_stringtable.add(tag.key()));
+                        m_tags.push_back(m_stringtable.add(tag.value()));
                     }
+                    m_tags.push_back(0);
                 }
 
+                std::string serialize() const {
+                    std::string data;
+                    protozero::pbf_builder<OSMFormat::DenseNodes> pbf_dense_nodes(data);
 
-                ///// MetaData helper /////
-
-                /**
-                 * convert a double lat or lon value to an int, respecting the current blocks granularity
-                 */
-                int64_t lonlat2int(double lonlat) {
-                    return static_cast<int64_t>(std::round(lonlat * OSMPBF::lonlat_resolution / location_granularity()));
-                }
-
-                /**
-                 * convert a timestamp to an int, respecting the current blocks granularity
-                 */
-                int64_t timestamp2int(time_t timestamp) {
-                    return static_cast<int64_t>(std::round(timestamp * (1000.0 / date_granularity())));
-                }
+                    pbf_dense_nodes.add_packed_sint64(OSMFormat::DenseNodes::packed_sint64_id, m_ids.cbegin(), m_ids.cend());
 
-                /**
-                 * helper function used in the write()-calls to apply common information from an osmium-object
-                 * onto a pbf-object.
-                 *
-                 * TPBFObject is either OSMPBF::Node, OSMPBF::Way or OSMPBF::Relation.
-                 */
-                template <class TPBFObject>
-                void apply_common_info(const osmium::OSMObject& in, TPBFObject* out) {
-                    // set the object-id
-                    out->set_id(in.id());
-
-                    // iterate over all tags and set the keys and vals, recording the strings in the
-                    // interim StringTable and storing the interim ids
-                    for (const auto& tag : in.tags()) {
-                        out->add_keys(string_table.record_string(tag.key()));
-                        out->add_vals(string_table.record_string(tag.value()));
-                    }
+                    if (m_add_metadata) {
+                        protozero::pbf_builder<OSMFormat::DenseInfo> pbf_dense_info(pbf_dense_nodes, OSMFormat::DenseNodes::optional_DenseInfo_denseinfo);
+                        pbf_dense_info.add_packed_int32(OSMFormat::DenseInfo::packed_int32_version, m_versions.cbegin(), m_versions.cend());
+                        pbf_dense_info.add_packed_sint64(OSMFormat::DenseInfo::packed_sint64_timestamp, m_timestamps.cbegin(), m_timestamps.cend());
+                        pbf_dense_info.add_packed_sint64(OSMFormat::DenseInfo::packed_sint64_changeset, m_changesets.cbegin(), m_changesets.cend());
+                        pbf_dense_info.add_packed_sint32(OSMFormat::DenseInfo::packed_sint32_uid, m_uids.cbegin(), m_uids.cend());
+                        pbf_dense_info.add_packed_sint32(OSMFormat::DenseInfo::packed_sint32_user_sid, m_user_sids.cbegin(), m_user_sids.cend());
 
-                    if (m_should_add_metadata) {
-                        // add an info-section to the pbf object and set the meta-info on it
-                        OSMPBF::Info* out_info = out->mutable_info();
                         if (m_add_visible) {
-                            out_info->set_visible(in.visible());
+                            pbf_dense_info.add_packed_bool(OSMFormat::DenseInfo::packed_bool_visible, m_visibles.cbegin(), m_visibles.cend());
                         }
-                        out_info->set_version(static_cast<::google::protobuf::int32>(in.version()));
-                        out_info->set_timestamp(timestamp2int(in.timestamp()));
-                        out_info->set_changeset(in.changeset());
-                        out_info->set_uid(static_cast<::google::protobuf::int32>(in.uid()));
-                        out_info->set_user_sid(string_table.record_string(in.user()));
                     }
+
+                    pbf_dense_nodes.add_packed_sint64(OSMFormat::DenseNodes::packed_sint64_lat, m_lats.cbegin(), m_lats.cend());
+                    pbf_dense_nodes.add_packed_sint64(OSMFormat::DenseNodes::packed_sint64_lon, m_lons.cbegin(), m_lons.cend());
+
+                    pbf_dense_nodes.add_packed_int32(OSMFormat::DenseNodes::packed_int32_keys_vals, m_tags.cbegin(), m_tags.cend());
+
+                    return data;
                 }
 
+            }; // class DenseNodes
 
-                ///// High-Level Block writing /////
+            class PrimitiveBlock {
 
-                /**
-                 * store the current pbf_header_block into a Blob and clear this struct afterwards.
-                 */
-                void store_header_block() {
-                    if (debug && has_debug_level(1)) {
-                        std::cerr << "storing header block" << std::endl;
-                    }
+                std::string m_pbf_primitive_group_data;
+                protozero::pbf_builder<OSMFormat::PrimitiveGroup> m_pbf_primitive_group;
+                StringTable m_stringtable;
+                DenseNodes m_dense_nodes;
+                OSMFormat::PrimitiveGroup m_type;
+                int m_count;
 
-                    std::promise<std::string> promise;
-                    m_output_queue.push(promise.get_future());
-                    promise.set_value(serialize_blob("OSMHeader", pbf_header_block, m_use_compression));
+            public:
 
-                    pbf_header_block.Clear();
+                PrimitiveBlock(bool add_metadata, bool add_visible) :
+                    m_pbf_primitive_group_data(),
+                    m_pbf_primitive_group(m_pbf_primitive_group_data),
+                    m_stringtable(),
+                    m_dense_nodes(m_stringtable, add_metadata, add_visible),
+                    m_type(OSMFormat::PrimitiveGroup::unknown),
+                    m_count(0) {
                 }
 
-                /**
-                 * store the interim StringTable to the current pbf_primitive_block, map all interim string ids
-                 * to real StringTable ids and then store the current pbf_primitive_block into a Blob and clear
-                 * this struct and all related pointers and maps afterwards.
-                 */
-                void store_primitive_block() {
-                    if (debug && has_debug_level(1)) {
-                        std::cerr << "storing primitive block with " << primitive_block_contents << " items" << std::endl;
+                const std::string& group_data() {
+                    if (type() == OSMFormat::PrimitiveGroup::optional_DenseNodes_dense) {
+                        m_pbf_primitive_group.add_message(OSMFormat::PrimitiveGroup::optional_DenseNodes_dense, m_dense_nodes.serialize());
                     }
+                    return m_pbf_primitive_group_data;
+                }
 
-                    // set the granularity
-                    pbf_primitive_block.set_granularity(location_granularity());
-                    pbf_primitive_block.set_date_granularity(date_granularity());
-
-                    string_table.store_stringtable(pbf_primitive_block.mutable_stringtable(), m_sort_stringtables);
+                void reset(OSMFormat::PrimitiveGroup type) {
+                    m_pbf_primitive_group_data.clear();
+                    m_stringtable.clear();
+                    m_dense_nodes.clear();
+                    m_type = type;
+                    m_count = 0;
+                }
 
-                    if (m_sort_stringtables) {
-                        map_string_ids();
-                    } else {
-                        delta_encode_string_ids();
+                void write_stringtable(protozero::pbf_builder<OSMFormat::StringTable>& pbf_string_table) {
+                    for (const char* s : m_stringtable) {
+                        pbf_string_table.add_bytes(OSMFormat::StringTable::repeated_bytes_s, s);
                     }
+                }
 
-                    std::promise<std::string> promise;
-                    m_output_queue.push(promise.get_future());
-                    promise.set_value(serialize_blob("OSMData", pbf_primitive_block, m_use_compression));
+                protozero::pbf_builder<OSMFormat::PrimitiveGroup>& group() {
+                    ++m_count;
+                    return m_pbf_primitive_group;
+                }
 
-                    // clear the PrimitiveBlock struct
-                    pbf_primitive_block.Clear();
+                void add_dense_node(const osmium::Node& node) {
+                    m_dense_nodes.add_node(node);
+                    ++m_count;
+                }
 
-                    // clear the interim StringTable and its id map
-                    string_table.clear();
+                size_t add_string(const char* s) {
+                    return m_stringtable.add(s);
+                }
 
-                    // reset the delta variables
-                    m_delta_id.clear();
-                    m_delta_lat.clear();
-                    m_delta_lon.clear();
-                    m_delta_timestamp.clear();
-                    m_delta_changeset.clear();
-                    m_delta_uid.clear();
-                    m_delta_user_sid.clear();
+                int count() const {
+                    return m_count;
+                }
 
-                    // reset the contents-counter to zero
-                    primitive_block_contents = 0;
-                    primitive_block_size = 0;
+                OSMFormat::PrimitiveGroup type() const {
+                    return m_type;
+                }
 
-                    // reset the node/way/relation pointers to nullptr
-                    pbf_nodes = nullptr;
-                    pbf_ways = nullptr;
-                    pbf_relations = nullptr;
+                size_t size() const {
+                    return m_pbf_primitive_group_data.size() + m_stringtable.size() + m_dense_nodes.size();
                 }
 
                 /**
-                 * this little function checks primitive_block_contents counter against its maximum and calls
-                 * store_primitive_block to flush the block to the disk when it's reached. It's also responsible
-                 * for increasing this counter.
-                 *
-                 * this function also checks the estimated size of the current block and calls store_primitive_block
-                 * when the estimated size reaches buffer_fill_percent of the maximum uncompressed blob size.
+                 * The output buffer (block) will be filled to about
+                 * 95% and then written to disk. This leaves more than
+                 * enough space for the string table (which typically
+                 * needs about 0.1 to 0.3% of the block size).
                  */
-                void check_block_contents_counter() {
-                    if (primitive_block_contents >= max_block_contents) {
-                        store_primitive_block();
-                    } else if (primitive_block_size > OSMPBF::max_uncompressed_blob_size * buffer_fill_percent / 100) {
-                        if (debug && has_debug_level(1)) {
-                            std::cerr << "storing primitive_block with only " << primitive_block_contents << " items, because its ByteSize (" << primitive_block_size << ") reached " <<
-                                      (static_cast<float>(primitive_block_size) / static_cast<float>(OSMPBF::max_uncompressed_blob_size) * 100.0) << "% of the maximum blob-size" << std::endl;
-                        }
+                constexpr static size_t max_used_blob_size = max_uncompressed_blob_size * 95 / 100;
 
-                        store_primitive_block();
+                bool can_add(OSMFormat::PrimitiveGroup type) const {
+                    if (type != m_type) {
+                        return false;
                     }
-
-                    ++primitive_block_contents;
+                    if (count() >= max_entities_per_block) {
+                        return false;
+                    }
+                    return size() < max_used_blob_size;
                 }
 
+            }; // class PrimitiveBlock
 
-                ///// Block content writing /////
-
-                /**
-                 * Add a node to the block.
-                 *
-                 * @param node The node to add.
-                 */
-                void write_node(const osmium::Node& node) {
-                    // add a way to the group
-                    OSMPBF::Node* pbf_node = pbf_nodes->add_nodes();
-
-                    // copy the common meta-info from the osmium-object to the pbf-object
-                    apply_common_info(node, pbf_node);
+            class PBFOutputFormat : public osmium::io::detail::OutputFormat, public osmium::handler::Handler {
 
-                    // modify lat & lon to integers, respecting the block's granularity and copy
-                    // the ints to the pbf-object
-                    pbf_node->set_lon(lonlat2int(node.location().lon_without_check()));
-                    pbf_node->set_lat(lonlat2int(node.location().lat_without_check()));
-                }
+                /// Should nodes be encoded in DenseNodes?
+                bool m_use_dense_nodes;
 
                 /**
-                 * Add a node to the block using DenseNodes.
+                 * Should the PBF blobs contain zlib compressed data?
                  *
-                 * @param node The node to add.
+                 * The zlib compression is optional, it's possible to store the
+                 * blobs in raw format. Disabling the compression can improve
+                 * the writing speed a little but the output will be 2x to 3x
+                 * bigger.
                  */
-                void write_dense_node(const osmium::Node& node) {
-                    // add a DenseNodes-Section to the PrimitiveGroup
-                    OSMPBF::DenseNodes* dense = pbf_nodes->mutable_dense();
+                bool m_use_compression;
 
-                    // copy the id, delta encoded
-                    dense->add_id(m_delta_id.update(node.id()));
+                /// Should metadata of objects be written?
+                bool m_add_metadata;
 
-                    // copy the longitude, delta encoded
-                    dense->add_lon(m_delta_lon.update(lonlat2int(node.location().lon_without_check())));
+                /// Should the visible flag be added to objects?
+                bool m_add_visible;
 
-                    // copy the latitude, delta encoded
-                    dense->add_lat(m_delta_lat.update(lonlat2int(node.location().lat_without_check())));
+                PrimitiveBlock m_primitive_block;
 
-                    // in the densenodes structure keys and vals are encoded in an intermixed
-                    // array, individual nodes are seperated by a value of 0 (0 in the StringTable
-                    // is always unused)
-                    // so for three nodes the keys_vals array may look like this: 3 5 2 1 0 0 8 5
-                    // the first node has two tags (3=>5 and 2=>1), the second node does not
-                    // have any tags and the third node has a single tag (8=>5)
-                    for (const auto& tag : node.tags()) {
-                        dense->add_keys_vals(string_table.record_string(tag.key()));
-                        dense->add_keys_vals(string_table.record_string(tag.value()));
+                void store_primitive_block() {
+                    if (m_primitive_block.count() == 0) {
+                        return;
                     }
-                    dense->add_keys_vals(0);
-
-                    if (m_should_add_metadata) {
-                        // add a DenseInfo-Section to the PrimitiveGroup
-                        OSMPBF::DenseInfo* denseinfo = dense->mutable_denseinfo();
-
-                        denseinfo->add_version(static_cast<::google::protobuf::int32>(node.version()));
-
-                        if (m_add_visible) {
-                            denseinfo->add_visible(node.visible());
-                        }
 
-                        // copy the timestamp, delta encoded
-                        denseinfo->add_timestamp(m_delta_timestamp.update(timestamp2int(node.timestamp())));
+                    std::string primitive_block_data;
+                    protozero::pbf_builder<OSMFormat::PrimitiveBlock> primitive_block(primitive_block_data);
 
-                        // copy the changeset, delta encoded
-                        denseinfo->add_changeset(m_delta_changeset.update(node.changeset()));
-
-                        // copy the user id, delta encoded
-                        denseinfo->add_uid(static_cast<::google::protobuf::int32>(m_delta_uid.update(node.uid())));
-
-                        // record the user-name to the interim stringtable and copy the
-                        // interim string-id to the pbf-object
-                        denseinfo->add_user_sid(string_table.record_string(node.user()));
+                    {
+                        protozero::pbf_builder<OSMFormat::StringTable> pbf_string_table(primitive_block, OSMFormat::PrimitiveBlock::required_StringTable_stringtable);
+                        m_primitive_block.write_stringtable(pbf_string_table);
                     }
-                }
-
-                /**
-                 * Add a way to the block.
-                 *
-                 * @param way The way to add.
-                 */
-                void write_way(const osmium::Way& way) {
-                    // add a way to the group
-                    OSMPBF::Way* pbf_way = pbf_ways->add_ways();
 
-                    // copy the common meta-info from the osmium-object to the pbf-object
-                    apply_common_info(way, pbf_way);
+                    primitive_block.add_message(OSMFormat::PrimitiveBlock::repeated_PrimitiveGroup_primitivegroup, m_primitive_block.group_data());
 
-                    // last way-node-id used for delta-encoding
-                    osmium::util::DeltaEncode<int64_t> delta_id;
-
-                    for (const auto& node_ref : way.nodes()) {
-                        // copy the way-node-id, delta encoded
-                        pbf_way->add_refs(delta_id.update(node_ref.ref()));
-                    }
-
-                    // count up blob size by the size of the Way
-                    primitive_block_size += pbf_way->ByteSize();
+                    std::promise<std::string> promise;
+                    m_output_queue.push(promise.get_future());
+                    promise.set_value(serialize_blob("OSMData", primitive_block_data, m_use_compression));
                 }
 
-                /**
-                 * Add a relation to the block.
-                 *
-                 * @param relation The relation to add.
-                 */
-                void write_relation(const osmium::Relation& relation) {
-                    // add a relation to the group
-                    OSMPBF::Relation* pbf_relation = pbf_relations->add_relations();
-
-                    // copy the common meta-info from the osmium-object to the pbf-object
-                    apply_common_info(relation, pbf_relation);
-
-                    osmium::util::DeltaEncode<int64_t> delta_id;
-
-                    for (const auto& member : relation.members()) {
-                        // record the relation-member role to the interim stringtable and copy the
-                        // interim string-id to the pbf-object
-                        pbf_relation->add_roles_sid(string_table.record_string(member.role()));
-
-                        // copy the relation-member-id, delta encoded
-                        pbf_relation->add_memids(delta_id.update(member.ref()));
-
-                        // copy the relation-member-type, mapped to the OSMPBF enum
-                        pbf_relation->add_types(item_type_to_osmpbf_membertype(member.type()));
+                template <typename T>
+                void add_meta(const osmium::OSMObject& object, T& pbf_object) {
+                    const osmium::TagList& tags = object.tags();
+
+                    auto map_tag_key = [this](const osmium::Tag& tag) -> size_t {
+                        return m_primitive_block.add_string(tag.key());
+                    };
+                    auto map_tag_value = [this](const osmium::Tag& tag) -> size_t {
+                        return m_primitive_block.add_string(tag.value());
+                    };
+
+                    pbf_object.add_packed_uint32(T::enum_type::packed_uint32_keys,
+                        boost::make_transform_iterator(tags.begin(), map_tag_key),
+                        boost::make_transform_iterator(tags.end(), map_tag_key));
+
+                    pbf_object.add_packed_uint32(T::enum_type::packed_uint32_vals,
+                        boost::make_transform_iterator(tags.begin(), map_tag_value),
+                        boost::make_transform_iterator(tags.end(), map_tag_value));
+
+                    if (m_add_metadata) {
+                        protozero::pbf_builder<OSMFormat::Info> pbf_info(pbf_object, T::enum_type::optional_Info_info);
+
+                        pbf_info.add_int32(OSMFormat::Info::optional_int32_version, object.version());
+                        pbf_info.add_int64(OSMFormat::Info::optional_int64_timestamp, object.timestamp());
+                        pbf_info.add_int64(OSMFormat::Info::optional_int64_changeset, object.changeset());
+                        pbf_info.add_int32(OSMFormat::Info::optional_int32_uid, object.uid());
+                        pbf_info.add_uint32(OSMFormat::Info::optional_uint32_user_sid, m_primitive_block.add_string(object.user()));
+                        if (m_add_visible) {
+                            pbf_info.add_bool(OSMFormat::Info::optional_bool_visible, object.visible());
+                        }
                     }
-
-                    // count up blob size by the size of the Relation
-                    primitive_block_size += pbf_relation->ByteSize();
                 }
 
-                // objects of this class can't be copied
                 PBFOutputFormat(const PBFOutputFormat&) = delete;
                 PBFOutputFormat& operator=(const PBFOutputFormat&) = delete;
 
             public:
 
-                /**
-                 * Create PBFOutputFormat object from File.
-                 */
                 explicit PBFOutputFormat(const osmium::io::File& file, data_queue_type& output_queue) :
                     OutputFormat(file, output_queue),
-                    pbf_header_block(),
-                    pbf_primitive_block(),
-                    pbf_nodes(nullptr),
-                    pbf_ways(nullptr),
-                    pbf_relations(nullptr),
-                    m_location_granularity(pbf_primitive_block.granularity()),
-                    m_date_granularity(pbf_primitive_block.date_granularity()),
+                    m_use_dense_nodes(file.get("pbf_dense_nodes") != "false"),
+                    m_use_compression(file.get("pbf_compression") != "none" && file.get("pbf_compression") != "false"),
+                    m_add_metadata(file.get("pbf_add_metadata") != "false" && file.get("add_metadata") != "false"),
                     m_add_visible(file.has_multiple_object_versions()),
-                    primitive_block_contents(0),
-                    primitive_block_size(0),
-                    string_table(),
-                    m_delta_id(),
-                    m_delta_lat(),
-                    m_delta_lon(),
-                    m_delta_timestamp(),
-                    m_delta_changeset(),
-                    m_delta_uid(),
-                    m_delta_user_sid(),
-                    debug(true) {
-                    GOOGLE_PROTOBUF_VERIFY_VERSION;
-                    if (file.get("pbf_dense_nodes") == "false") {
-                        m_use_dense_nodes = false;
-                    }
-                    if (file.get("pbf_compression") == "none" || file.get("pbf_compression") == "false") {
-                        m_use_compression = false;
-                    }
-                    if (file.get("pbf_sort_stringtables") == "false") {
-                        m_sort_stringtables = false;
-                    }
-                    if (file.get("pbf_add_metadata") == "false") {
-                        m_should_add_metadata = false;
-                    }
+                    m_primitive_block(m_add_metadata, m_add_visible) {
                 }
 
                 void write_buffer(osmium::memory::Buffer&& buffer) override final {
                     osmium::apply(buffer.cbegin(), buffer.cend(), *this);
                 }
 
+                void write_header(const osmium::io::Header& header) override final {
+                    std::string data;
+                    protozero::pbf_builder<OSMFormat::HeaderBlock> pbf_header_block(data);
 
-                /**
-                 * getter to access the granularity
-                 */
-                int location_granularity() const {
-                    return m_location_granularity;
-                }
-
-                /**
-                 * setter to set the granularity
-                 */
-                PBFOutputFormat& location_granularity(int g) {
-                    m_location_granularity = g;
-                    return *this;
-                }
-
-
-                /**
-                 * getter to access the date_granularity
-                 */
-                int date_granularity() const {
-                    return m_date_granularity;
-                }
-
-                /**
-                 * Set date granularity.
-                 */
-                PBFOutputFormat& date_granularity(int g) {
-                    m_date_granularity = g;
-                    return *this;
-                }
+                    if (!header.boxes().empty()) {
+                        protozero::pbf_builder<OSMFormat::HeaderBBox> pbf_header_bbox(pbf_header_block, OSMFormat::HeaderBlock::optional_HeaderBBox_bbox);
 
+                        osmium::Box box = header.joined_boxes();
+                        pbf_header_bbox.add_sint64(OSMFormat::HeaderBBox::required_sint64_left,   box.bottom_left().lon() * lonlat_resolution);
+                        pbf_header_bbox.add_sint64(OSMFormat::HeaderBBox::required_sint64_right,  box.top_right().lon()   * lonlat_resolution);
+                        pbf_header_bbox.add_sint64(OSMFormat::HeaderBBox::required_sint64_top,    box.top_right().lat()   * lonlat_resolution);
+                        pbf_header_bbox.add_sint64(OSMFormat::HeaderBBox::required_sint64_bottom, box.bottom_left().lat() * lonlat_resolution);
+                    }
 
-                /**
-                 * Initialize the writing process.
-                 *
-                 * This initializes the header-block, sets the required-features and
-                 * the writing-program and adds the obligatory StringTable-Index 0.
-                 */
-                void write_header(const osmium::io::Header& header) override final {
-                    // add the schema version as required feature to the HeaderBlock
-                    pbf_header_block.add_required_features("OsmSchema-V0.6");
+                    pbf_header_block.add_string(OSMFormat::HeaderBlock::repeated_string_required_features, "OsmSchema-V0.6");
 
-                    // when the densenodes-feature is used, add DenseNodes as required feature
                     if (m_use_dense_nodes) {
-                        pbf_header_block.add_required_features("DenseNodes");
+                        pbf_header_block.add_string(OSMFormat::HeaderBlock::repeated_string_required_features, "DenseNodes");
                     }
 
-                    // when the resulting file will carry history information, add
-                    // HistoricalInformation as required feature
                     if (m_file.has_multiple_object_versions()) {
-                        pbf_header_block.add_required_features("HistoricalInformation");
+                        pbf_header_block.add_string(OSMFormat::HeaderBlock::repeated_string_required_features, "HistoricalInformation");
                     }
 
-                    // set the writing program
-                    pbf_header_block.set_writingprogram(header.get("generator"));
-
-                    if (!header.boxes().empty()) {
-                        OSMPBF::HeaderBBox* pbf_bbox = pbf_header_block.mutable_bbox();
-                        osmium::Box box = header.joined_boxes();
-                        pbf_bbox->set_left(static_cast<::google::protobuf::int64>(box.bottom_left().lon() * OSMPBF::lonlat_resolution));
-                        pbf_bbox->set_bottom(static_cast<::google::protobuf::int64>(box.bottom_left().lat() * OSMPBF::lonlat_resolution));
-                        pbf_bbox->set_right(static_cast<::google::protobuf::int64>(box.top_right().lon() * OSMPBF::lonlat_resolution));
-                        pbf_bbox->set_top(static_cast<::google::protobuf::int64>(box.top_right().lat() * OSMPBF::lonlat_resolution));
-                    }
+                    pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_writingprogram, header.get("generator"));
 
                     std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp");
                     if (!osmosis_replication_timestamp.empty()) {
                         osmium::Timestamp ts(osmosis_replication_timestamp.c_str());
-                        pbf_header_block.set_osmosis_replication_timestamp(ts);
+                        pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_timestamp, ts);
                     }
 
                     std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number");
                     if (!osmosis_replication_sequence_number.empty()) {
-                        pbf_header_block.set_osmosis_replication_sequence_number(std::atoll(osmosis_replication_sequence_number.c_str()));
+                        pbf_header_block.add_int64(OSMFormat::HeaderBlock::optional_int64_osmosis_replication_sequence_number, std::atoll(osmosis_replication_sequence_number.c_str()));
                     }
 
                     std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url");
                     if (!osmosis_replication_base_url.empty()) {
-                        pbf_header_block.set_osmosis_replication_base_url(osmosis_replication_base_url);
+                        pbf_header_block.add_string(OSMFormat::HeaderBlock::optional_string_osmosis_replication_base_url, osmosis_replication_base_url);
                     }
 
-                    store_header_block();
+                    std::promise<std::string> promise;
+                    m_output_queue.push(promise.get_future());
+                    promise.set_value(serialize_blob("OSMHeader", data, m_use_compression));
                 }
 
-                /**
-                 * Add a node to the pbf.
-                 *
-                 * A call to this method won't write the node to the file directly but
-                 * cache it for later bulk-writing. Calling final() ensures that everything
-                 * gets written and every file pointer is closed.
-                 */
-                void node(const osmium::Node& node) {
-                    // first of we check the contents-counter which may flush the cached nodes to
-                    // disk if the limit is reached. This call also increases the contents-counter
-                    check_block_contents_counter();
-
-                    if (debug && has_debug_level(2)) {
-                        std::cerr << "node " << node.id() << " v" << node.version() << std::endl;
-                    }
-
-                    // if no PrimitiveGroup for nodes has been added, add one and save the pointer
-                    if (!pbf_nodes) {
-                        pbf_nodes = pbf_primitive_block.add_primitivegroup();
+                void switch_primitive_block_type(OSMFormat::PrimitiveGroup type) {
+                    if (!m_primitive_block.can_add(type)) {
+                        store_primitive_block();
+                        m_primitive_block.reset(type);
                     }
+                }
 
+                void node(const osmium::Node& node) {
                     if (m_use_dense_nodes) {
-                        write_dense_node(node);
-                    } else {
-                        write_node(node);
+                        switch_primitive_block_type(OSMFormat::PrimitiveGroup::optional_DenseNodes_dense);
+                        m_primitive_block.add_dense_node(node);
+                        return;
                     }
+
+                    switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Node_nodes);
+                    protozero::pbf_builder<OSMFormat::Node> pbf_node{ m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Node_nodes };
+
+                    pbf_node.add_sint64(OSMFormat::Node::required_sint64_id, node.id());
+                    add_meta(node, pbf_node);
+
+                    pbf_node.add_sint64(OSMFormat::Node::required_sint64_lat, lonlat2int(node.location().lat_without_check()));
+                    pbf_node.add_sint64(OSMFormat::Node::required_sint64_lon, lonlat2int(node.location().lon_without_check()));
                 }
 
-                /**
-                 * Add a way to the pbf.
-                 *
-                 * A call to this method won't write the way to the file directly but
-                 * cache it for later bulk-writing. Calling final() ensures that everything
-                 * gets written and every file pointer is closed.
-                 */
                 void way(const osmium::Way& way) {
-                    // first of we check the contents-counter which may flush the cached ways to
-                    // disk if the limit is reached. This call also increases the contents-counter
-                    check_block_contents_counter();
+                    switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Way_ways);
+                    protozero::pbf_builder<OSMFormat::Way> pbf_way{ m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Way_ways };
 
-                    // if no PrimitiveGroup for nodes has been added, add one and save the pointer
-                    if (!pbf_ways) {
-                        pbf_ways = pbf_primitive_block.add_primitivegroup();
-                    }
+                    pbf_way.add_int64(OSMFormat::Way::required_int64_id, way.id());
+                    add_meta(way, pbf_way);
+
+                    static auto map_node_ref = [](osmium::NodeRefList::const_iterator node_ref) noexcept -> osmium::object_id_type {
+                        return node_ref->ref();
+                    };
+                    typedef osmium::util::DeltaEncodeIterator<osmium::NodeRefList::const_iterator, decltype(map_node_ref), osmium::object_id_type> it_type;
 
-                    write_way(way);
+                    const auto& nodes = way.nodes();
+                    it_type first { nodes.cbegin(), nodes.cend(), map_node_ref };
+                    it_type last { nodes.cend(), nodes.cend(), map_node_ref };
+                    pbf_way.add_packed_sint64(OSMFormat::Way::packed_sint64_refs, first, last);
                 }
 
-                /**
-                 * Add a relation to the pbf.
-                 *
-                 * A call to this method won't write the way to the file directly but
-                 * cache it for later bulk-writing. Calling final() ensures that everything
-                 * gets written and every file pointer is closed.
-                 */
                 void relation(const osmium::Relation& relation) {
-                    // first of we check the contents-counter which may flush the cached relations to
-                    // disk if the limit is reached. This call also increases the contents-counter
-                    check_block_contents_counter();
-
-                    // if no PrimitiveGroup for relations has been added, add one and save the pointer
-                    if (!pbf_relations) {
-                        pbf_relations = pbf_primitive_block.add_primitivegroup();
-                    }
-
-                    write_relation(relation);
+                    switch_primitive_block_type(OSMFormat::PrimitiveGroup::repeated_Relation_relations);
+                    protozero::pbf_builder<OSMFormat::Relation> pbf_relation { m_primitive_block.group(), OSMFormat::PrimitiveGroup::repeated_Relation_relations };
+
+                    pbf_relation.add_int64(OSMFormat::Relation::required_int64_id, relation.id());
+                    add_meta(relation, pbf_relation);
+
+                    auto map_member_role = [this](const osmium::RelationMember& member) -> size_t {
+                        return m_primitive_block.add_string(member.role());
+                    };
+                    pbf_relation.add_packed_int32(OSMFormat::Relation::packed_int32_roles_sid,
+                        boost::make_transform_iterator(relation.members().begin(), map_member_role),
+                        boost::make_transform_iterator(relation.members().end(), map_member_role));
+
+                    static auto map_member_ref = [](osmium::RelationMemberList::const_iterator member) noexcept -> osmium::object_id_type {
+                        return member->ref();
+                    };
+                    typedef osmium::util::DeltaEncodeIterator<osmium::RelationMemberList::const_iterator, decltype(map_member_ref), osmium::object_id_type> it_type;
+                    const auto& members = relation.members();
+                    it_type first { members.cbegin(), members.cend(), map_member_ref };
+                    it_type last { members.cend(), members.cend(), map_member_ref };
+                    pbf_relation.add_packed_sint64(OSMFormat::Relation::packed_sint64_memids, first, last);
+
+                    static auto map_member_type = [](const osmium::RelationMember& member) noexcept -> int {
+                        return osmium::item_type_to_nwr_index(member.type());
+                    };
+                    pbf_relation.add_packed_int32(OSMFormat::Relation::packed_MemberType_types,
+                        boost::make_transform_iterator(relation.members().begin(), map_member_type),
+                        boost::make_transform_iterator(relation.members().end(), map_member_type));
                 }
 
                 /**
-                 * Finalize the writing process, flush any open primitive blocks to the file and
-                 * close the file.
+                 * Finalize the writing process, flush any open primitive
+                 * blocks to the file and close the file.
                  */
                 void close() override final {
-                    if (debug && has_debug_level(1)) {
-                        std::cerr << "finishing" << std::endl;
-                    }
-
-                    // if the current block contains any elements, flush it to the protobuf
-                    if (primitive_block_contents > 0) {
-                        store_primitive_block();
-                    }
+                    store_primitive_block();
 
                     std::promise<std::string> promise;
                     m_output_queue.push(promise.get_future());
@@ -930,10 +578,15 @@ namespace osmium {
 
             namespace {
 
+// we want the register_output_format() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
                 const bool registered_pbf_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::pbf,
                     [](const osmium::io::File& file, data_queue_type& output_queue) {
                         return new osmium::io::detail::PBFOutputFormat(file, output_queue);
                 });
+#pragma GCC diagnostic pop
 
             } // anonymous namespace
 
diff --git a/include/osmium/io/detail/pbf_parser.hpp b/include/osmium/io/detail/pbf_parser.hpp
deleted file mode 100644
index f626b0b..0000000
--- a/include/osmium/io/detail/pbf_parser.hpp
+++ /dev/null
@@ -1,456 +0,0 @@
-#ifndef OSMIUM_IO_DETAIL_PBF_PRIMITIVE_BLOCK_PARSER_HPP
-#define OSMIUM_IO_DETAIL_PBF_PRIMITIVE_BLOCK_PARSER_HPP
-
-/*
-
-This file is part of Osmium (http://osmcode.org/libosmium).
-
-Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-
-*/
-
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <algorithm>
-
-#include <osmpbf/osmpbf.h>
-
-#include <osmium/builder/osm_object_builder.hpp>
-#include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_type_conv.hpp>
-#include <osmium/io/detail/zlib.hpp>
-#include <osmium/io/header.hpp>
-#include <osmium/osm/location.hpp>
-#include <osmium/osm/node.hpp>
-#include <osmium/osm/types.hpp>
-#include <osmium/memory/buffer.hpp>
-#include <osmium/osm/entity_bits.hpp>
-#include <osmium/util/cast.hpp>
-
-namespace osmium {
-
-    namespace io {
-
-        namespace detail {
-
-            class PBFPrimitiveBlockParser {
-
-                static constexpr size_t initial_buffer_size = 2 * 1024 * 1024;
-
-                const std::string& m_data;
-
-                const OSMPBF::StringTable* m_stringtable;
-                int64_t m_lon_offset;
-                int64_t m_lat_offset;
-                int64_t m_date_factor;
-                int32_t m_granularity;
-
-                osmium::osm_entity_bits::type m_read_types;
-
-                osmium::memory::Buffer m_buffer;
-
-                PBFPrimitiveBlockParser(const PBFPrimitiveBlockParser&) = delete;
-                PBFPrimitiveBlockParser(PBFPrimitiveBlockParser&&) = delete;
-
-                PBFPrimitiveBlockParser& operator=(const PBFPrimitiveBlockParser&) = delete;
-                PBFPrimitiveBlockParser& operator=(PBFPrimitiveBlockParser&&) = delete;
-
-            public:
-
-                explicit PBFPrimitiveBlockParser(const std::string& data, osmium::osm_entity_bits::type read_types) :
-                    m_data(data),
-                    m_stringtable(nullptr),
-                    m_lon_offset(0),
-                    m_lat_offset(0),
-                    m_date_factor(1000),
-                    m_granularity(100),
-                    m_read_types(read_types),
-                    m_buffer(initial_buffer_size) {
-                }
-
-                ~PBFPrimitiveBlockParser() = default;
-
-                osmium::memory::Buffer operator()() {
-                    OSMPBF::PrimitiveBlock pbf_primitive_block;
-                    if (!pbf_primitive_block.ParseFromString(m_data)) {
-                        throw osmium::pbf_error("failed to parse PrimitiveBlock");
-                    }
-
-                    m_stringtable = &pbf_primitive_block.stringtable();
-                    m_lon_offset  = pbf_primitive_block.lon_offset();
-                    m_lat_offset  = pbf_primitive_block.lat_offset();
-                    m_date_factor = pbf_primitive_block.date_granularity() / 1000;
-                    m_granularity = pbf_primitive_block.granularity();
-
-                    for (int i = 0; i < pbf_primitive_block.primitivegroup_size(); ++i) {
-                        const OSMPBF::PrimitiveGroup& group = pbf_primitive_block.primitivegroup(i);
-
-                        if (group.has_dense())  {
-                            if (m_read_types & osmium::osm_entity_bits::node) parse_dense_node_group(group);
-                        } else if (group.ways_size() != 0) {
-                            if (m_read_types & osmium::osm_entity_bits::way) parse_way_group(group);
-                        } else if (group.relations_size() != 0) {
-                            if (m_read_types & osmium::osm_entity_bits::relation) parse_relation_group(group);
-                        } else if (group.nodes_size() != 0) {
-                            if (m_read_types & osmium::osm_entity_bits::node) parse_node_group(group);
-                        } else {
-                            throw osmium::pbf_error("group of unknown type");
-                        }
-                    }
-
-                    return std::move(m_buffer);
-                }
-
-            private:
-
-                template <class TBuilder, class TPBFObject>
-                void parse_attributes(TBuilder& builder, const TPBFObject& pbf_object) {
-                    auto& object = builder.object();
-
-                    object.set_id(pbf_object.id());
-
-                    if (pbf_object.has_info()) {
-                        object.set_version(static_cast_with_assert<object_version_type>(pbf_object.info().version()))
-                            .set_changeset(static_cast_with_assert<changeset_id_type>(pbf_object.info().changeset()))
-                            .set_timestamp(pbf_object.info().timestamp() * m_date_factor)
-                            .set_uid_from_signed(pbf_object.info().uid());
-                        if (pbf_object.info().has_visible()) {
-                            object.set_visible(pbf_object.info().visible());
-                        }
-                        builder.add_user(m_stringtable->s(static_cast_with_assert<int>(pbf_object.info().user_sid())));
-                    } else {
-                        builder.add_user("", 1);
-                    }
-                }
-
-                void parse_node_group(const OSMPBF::PrimitiveGroup& group) {
-                    for (int i = 0; i < group.nodes_size(); ++i) {
-                        osmium::builder::NodeBuilder builder(m_buffer);
-                        const OSMPBF::Node& pbf_node = group.nodes(i);
-                        parse_attributes(builder, pbf_node);
-
-                        if (builder.object().visible()) {
-                            builder.object().set_location(osmium::Location(
-                                              (pbf_node.lon() * m_granularity + m_lon_offset) / (OSMPBF::lonlat_resolution / osmium::Location::coordinate_precision),
-                                              (pbf_node.lat() * m_granularity + m_lat_offset) / (OSMPBF::lonlat_resolution / osmium::Location::coordinate_precision)));
-                        }
-
-                        if (pbf_node.keys_size() > 0) {
-                            osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                            for (int tag = 0; tag < pbf_node.keys_size(); ++tag) {
-                                tl_builder.add_tag(m_stringtable->s(static_cast<int>(pbf_node.keys(tag))),
-                                                   m_stringtable->s(static_cast<int>(pbf_node.vals(tag))));
-                            }
-                        }
-
-                        m_buffer.commit();
-                    }
-                }
-
-                void parse_way_group(const OSMPBF::PrimitiveGroup& group) {
-                    for (int i = 0; i < group.ways_size(); ++i) {
-                        osmium::builder::WayBuilder builder(m_buffer);
-                        const OSMPBF::Way& pbf_way = group.ways(i);
-                        parse_attributes(builder, pbf_way);
-
-                        if (pbf_way.refs_size() > 0) {
-                            osmium::builder::WayNodeListBuilder wnl_builder(m_buffer, &builder);
-                            int64_t ref = 0;
-                            for (int n = 0; n < pbf_way.refs_size(); ++n) {
-                                ref += pbf_way.refs(n);
-                                wnl_builder.add_node_ref(ref);
-                            }
-                        }
-
-                        if (pbf_way.keys_size() > 0) {
-                            osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                            for (int tag = 0; tag < pbf_way.keys_size(); ++tag) {
-                                tl_builder.add_tag(m_stringtable->s(static_cast<int>(pbf_way.keys(tag))),
-                                                   m_stringtable->s(static_cast<int>(pbf_way.vals(tag))));
-                            }
-                        }
-
-                        m_buffer.commit();
-                    }
-                }
-
-                void parse_relation_group(const OSMPBF::PrimitiveGroup& group) {
-                    for (int i = 0; i < group.relations_size(); ++i) {
-                        osmium::builder::RelationBuilder builder(m_buffer);
-                        const OSMPBF::Relation& pbf_relation = group.relations(i);
-                        parse_attributes(builder, pbf_relation);
-
-                        if (pbf_relation.types_size() > 0) {
-                            osmium::builder::RelationMemberListBuilder rml_builder(m_buffer, &builder);
-                            int64_t ref = 0;
-                            for (int n = 0; n < pbf_relation.types_size(); ++n) {
-                                ref += pbf_relation.memids(n);
-                                rml_builder.add_member(osmpbf_membertype_to_item_type(pbf_relation.types(n)), ref, m_stringtable->s(pbf_relation.roles_sid(n)));
-                            }
-                        }
-
-                        if (pbf_relation.keys_size() > 0) {
-                            osmium::builder::TagListBuilder tl_builder(m_buffer, &builder);
-                            for (int tag = 0; tag < pbf_relation.keys_size(); ++tag) {
-                                tl_builder.add_tag(m_stringtable->s(static_cast<int>(pbf_relation.keys(tag))),
-                                                   m_stringtable->s(static_cast<int>(pbf_relation.vals(tag))));
-                            }
-                        }
-
-                        m_buffer.commit();
-                    }
-                }
-
-                int add_tags(const OSMPBF::DenseNodes& dense, int n, osmium::builder::NodeBuilder* builder) {
-                    if (n >= dense.keys_vals_size()) {
-                        return n;
-                    }
-
-                    if (dense.keys_vals(n) == 0) {
-                        return n+1;
-                    }
-
-                    osmium::builder::TagListBuilder tl_builder(m_buffer, builder);
-
-                    while (n < dense.keys_vals_size()) {
-                        int tag_key_pos = dense.keys_vals(n++);
-
-                        if (tag_key_pos == 0) {
-                            break;
-                        }
-
-                        tl_builder.add_tag(m_stringtable->s(tag_key_pos),
-                                           m_stringtable->s(dense.keys_vals(n)));
-
-                        ++n;
-                    }
-
-                    return n;
-                }
-
-                void parse_dense_node_group(const OSMPBF::PrimitiveGroup& group) {
-                    int64_t last_dense_id        = 0;
-                    int64_t last_dense_latitude  = 0;
-                    int64_t last_dense_longitude = 0;
-                    int64_t last_dense_uid       = 0;
-                    int64_t last_dense_user_sid  = 0;
-                    int64_t last_dense_changeset = 0;
-                    int64_t last_dense_timestamp = 0;
-                    int     last_dense_tag       = 0;
-
-                    const OSMPBF::DenseNodes& dense = group.dense();
-
-                    for (int i = 0; i < dense.id_size(); ++i) {
-                        bool visible = true;
-
-                        last_dense_id        += dense.id(i);
-                        last_dense_latitude  += dense.lat(i);
-                        last_dense_longitude += dense.lon(i);
-
-                        if (dense.has_denseinfo()) {
-                            last_dense_changeset += dense.denseinfo().changeset(i);
-                            last_dense_timestamp += dense.denseinfo().timestamp(i);
-                            last_dense_uid       += dense.denseinfo().uid(i);
-                            last_dense_user_sid  += dense.denseinfo().user_sid(i);
-                            if (dense.denseinfo().visible_size() > 0) {
-                                visible = dense.denseinfo().visible(i);
-                            }
-                            assert(last_dense_changeset >= 0);
-                            assert(last_dense_timestamp >= 0);
-                            assert(last_dense_uid >= -1);
-                            assert(last_dense_user_sid >= 0);
-                        }
-
-                        osmium::builder::NodeBuilder builder(m_buffer);
-                        osmium::Node& node = builder.object();
-
-                        node.set_id(last_dense_id);
-
-                        if (dense.has_denseinfo()) {
-                            auto v = dense.denseinfo().version(i);
-                            assert(v > 0);
-                            node.set_version(static_cast<osmium::object_version_type>(v));
-                            node.set_changeset(static_cast<osmium::changeset_id_type>(last_dense_changeset));
-                            node.set_timestamp(last_dense_timestamp * m_date_factor);
-                            node.set_uid_from_signed(static_cast<osmium::signed_user_id_type>(last_dense_uid));
-                            node.set_visible(visible);
-                            builder.add_user(m_stringtable->s(static_cast<int>(last_dense_user_sid)));
-                        } else {
-                            builder.add_user("", 1);
-                        }
-
-                        if (visible) {
-                            builder.object().set_location(osmium::Location(
-                                              (last_dense_longitude * m_granularity + m_lon_offset) / (OSMPBF::lonlat_resolution / osmium::Location::coordinate_precision),
-                                              (last_dense_latitude  * m_granularity + m_lat_offset) / (OSMPBF::lonlat_resolution / osmium::Location::coordinate_precision)));
-                        }
-
-                        last_dense_tag = add_tags(dense, last_dense_tag, &builder);
-                        m_buffer.commit();
-                    }
-                }
-
-            }; // class PBFPrimitiveBlockParser
-
-            /**
-             * PBF blobs can optionally be packed with the zlib algorithm.
-             * This function returns the raw data (if it was unpacked) or
-             * the unpacked data (if it was packed).
-             *
-             * @param input_data Reference to input data.
-             * @returns Unpacked data
-             * @throws osmium::pbf_error If there was a problem parsing the PBF
-             */
-            inline std::unique_ptr<const std::string> unpack_blob(const std::string& input_data) {
-                OSMPBF::Blob pbf_blob;
-                if (!pbf_blob.ParseFromString(input_data)) {
-                    throw osmium::pbf_error("failed to parse blob");
-                }
-
-                if (pbf_blob.has_raw()) {
-                    return std::unique_ptr<std::string>(pbf_blob.release_raw());
-                } else if (pbf_blob.has_zlib_data()) {
-                    auto raw_size = pbf_blob.raw_size();
-                    assert(raw_size >= 0);
-                    assert(raw_size <= OSMPBF::max_uncompressed_blob_size);
-                    return osmium::io::detail::zlib_uncompress(pbf_blob.zlib_data(), static_cast<unsigned long>(raw_size));
-                } else if (pbf_blob.has_lzma_data()) {
-                    throw osmium::pbf_error("lzma blobs not implemented");
-                } else {
-                    throw osmium::pbf_error("blob contains no data");
-                }
-            }
-
-            /**
-             * Parse blob as a HeaderBlock.
-             *
-             * @param input_buffer Blob data
-             * @returns Header object
-             * @throws osmium::pbf_error If there was a parsing error
-             */
-            inline osmium::io::Header parse_header_blob(const std::string& input_buffer) {
-                const std::unique_ptr<const std::string> data = unpack_blob(input_buffer);
-
-                OSMPBF::HeaderBlock pbf_header_block;
-                if (!pbf_header_block.ParseFromString(*data)) {
-                    throw osmium::pbf_error("failed to parse HeaderBlock");
-                }
-
-                osmium::io::Header header;
-                for (int i = 0; i < pbf_header_block.required_features_size(); ++i) {
-                    const std::string& feature = pbf_header_block.required_features(i);
-
-                    if (feature == "OsmSchema-V0.6") continue;
-                    if (feature == "DenseNodes") {
-                        header.set("pbf_dense_nodes", true);
-                        continue;
-                    }
-                    if (feature == "HistoricalInformation") {
-                        header.set_has_multiple_object_versions(true);
-                        continue;
-                    }
-
-                    throw osmium::pbf_error(std::string("required feature not supported: ") + feature);
-                }
-
-                for (int i = 0; i < pbf_header_block.optional_features_size(); ++i) {
-                    const std::string& feature = pbf_header_block.optional_features(i);
-                    header.set("pbf_optional_feature_" + std::to_string(i), feature);
-                }
-
-                if (pbf_header_block.has_writingprogram()) {
-                    header.set("generator", pbf_header_block.writingprogram());
-                }
-
-                if (pbf_header_block.has_bbox()) {
-                    const OSMPBF::HeaderBBox& pbf_bbox = pbf_header_block.bbox();
-                    const int64_t resolution_convert = OSMPBF::lonlat_resolution / osmium::Location::coordinate_precision;
-                    osmium::Box box;
-                    box.extend(osmium::Location(pbf_bbox.left()  / resolution_convert, pbf_bbox.bottom() / resolution_convert));
-                    box.extend(osmium::Location(pbf_bbox.right() / resolution_convert, pbf_bbox.top()    / resolution_convert));
-                    header.add_box(box);
-                }
-
-                if (pbf_header_block.has_osmosis_replication_timestamp()) {
-                    header.set("osmosis_replication_timestamp", osmium::Timestamp(pbf_header_block.osmosis_replication_timestamp()).to_iso());
-                }
-
-                if (pbf_header_block.has_osmosis_replication_sequence_number()) {
-                    header.set("osmosis_replication_sequence_number", std::to_string(pbf_header_block.osmosis_replication_sequence_number()));
-                }
-
-                if (pbf_header_block.has_osmosis_replication_base_url()) {
-                    header.set("osmosis_replication_base_url", pbf_header_block.osmosis_replication_base_url());
-                }
-
-                return header;
-            }
-
-            class DataBlobParser {
-
-                std::shared_ptr<std::string> m_input_buffer;
-                osmium::osm_entity_bits::type m_read_types;
-
-            public:
-
-                DataBlobParser(std::string&& input_buffer, osmium::osm_entity_bits::type read_types) :
-                    m_input_buffer(std::make_shared<std::string>(std::move(input_buffer))),
-                    m_read_types(read_types) {
-                    if (input_buffer.size() > OSMPBF::max_uncompressed_blob_size) {
-                        throw osmium::pbf_error(std::string("invalid blob size: " + std::to_string(input_buffer.size())));
-                    }
-                }
-/*
-                DataBlobParser(const DataBlobParser& other) :
-                    m_input_buffer(std::move(other.m_input_buffer)),
-                    m_read_types(other.m_read_types) {
-                }*/
-
-                DataBlobParser(const DataBlobParser&) = default;
-                DataBlobParser& operator=(const DataBlobParser&) = default;
-
-                DataBlobParser(DataBlobParser&&) = default;
-                DataBlobParser& operator=(DataBlobParser&&) = default;
-
-                ~DataBlobParser() = default;
-
-                osmium::memory::Buffer operator()() {
-                    const std::unique_ptr<const std::string> data = unpack_blob(*m_input_buffer);
-                    PBFPrimitiveBlockParser parser(*data, m_read_types);
-                    return parser();
-                }
-
-            }; // class DataBlobParser
-
-        } // namespace detail
-
-    } // namespace io
-
-} // namespace osmium
-
-#endif // OSMIUM_IO_DETAIL_PBF_PRIMITIVE_BLOCK_PARSER_HPP
diff --git a/include/osmium/io/detail/pbf_stringtable.hpp b/include/osmium/io/detail/pbf_stringtable.hpp
deleted file mode 100644
index 5f540f1..0000000
--- a/include/osmium/io/detail/pbf_stringtable.hpp
+++ /dev/null
@@ -1,218 +0,0 @@
-#ifndef OSMIUM_IO_DETAIL_PBF_STRINGTABLE_HPP
-#define OSMIUM_IO_DETAIL_PBF_STRINGTABLE_HPP
-
-/*
-
-This file is part of Osmium (http://osmcode.org/libosmium).
-
-Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-
-*/
-
-#include <algorithm>
-#include <cstdint>
-#include <iterator>
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <osmpbf/osmpbf.h>
-
-#include <osmium/util/cast.hpp>
-
-namespace osmium {
-
-    namespace io {
-
-        namespace detail {
-
-            /**
-             * StringTable management for PBF writer
-             *
-             * All strings are stored as indexes to rows in a StringTable. The StringTable contains
-             * one row for each used string, so strings that are used multiple times need to be
-             * stored only once. The StringTable is sorted by usage-count, so the most often used
-             * string is stored at index 1.
-             */
-            class StringTable {
-
-            public:
-
-                /// type for string IDs (interim and final)
-                typedef uint16_t string_id_type;
-
-            private:
-
-                /**
-                 * this is the struct used to build the StringTable. It is stored as
-                 * the value-part in the strings-map.
-                 *
-                 * when a new string is added to the map, its count is set to 0 and
-                 * the interim_id is set to the current size of the map. This interim_id
-                 * is then stored into the pbf-objects.
-                 *
-                 * before the PrimitiveBlock is serialized, the map is sorted by count
-                 * and stored into the pbf-StringTable. Afterwards the interim-ids are
-                 * mapped to the "real" id in the StringTable.
-                 *
-                 * this way often used strings get lower ids in the StringTable. As the
-                 * protobuf-serializer stores numbers in variable bit-lengths, lower
-                 * IDs means less used space in the resulting file.
-                 */
-                struct string_info {
-
-                    /// number of occurrences of this string
-                    uint16_t count;
-
-                    /// an intermediate-id
-                    string_id_type interim_id;
-
-                }; // struct string_info
-
-                /**
-                 * Interim StringTable, storing all strings that should be written to
-                 * the StringTable once the block is written to disk.
-                 */
-                typedef std::map<std::string, string_info> string2string_info_type;
-                string2string_info_type m_strings;
-
-                /**
-                 * This vector is used to map the interim IDs to real StringTable IDs after
-                 * writing all strings to the StringTable.
-                 */
-                typedef std::vector<string_id_type> interim_id2id_type;
-                interim_id2id_type m_id2id_map;
-
-                size_t m_size = 0;
-
-            public:
-
-                StringTable() {
-                }
-
-                friend bool operator<(const string_info& lhs, const string_info& rhs) {
-                    return lhs.count > rhs.count;
-                }
-
-                /**
-                 * record a string in the interim StringTable if it's missing, otherwise just increase its counter,
-                 * return the interim-id assigned to the string.
-                 */
-                string_id_type record_string(const std::string& string) {
-                    string_info& info = m_strings[string];
-                    if (info.interim_id == 0) {
-                        ++m_size;
-                        info.interim_id = static_cast_with_assert<string_id_type>(m_size);
-                    } else {
-                        info.count++;
-                    }
-                    return info.interim_id;
-                }
-
-                /**
-                 * Sort the interim StringTable and store it to the real protobuf StringTable.
-                 * while storing to the real table, this function fills the id2id_map with
-                 * pairs, mapping the interim-ids to final and real StringTable ids.
-                 *
-                 * Note that the m_strings table is a std::map and as such is sorted lexicographically.
-                 * When the transformation into the sortedby multimap is done, it gets sorted by
-                 * the count. The end result (at least with the glibc standard container/algorithm
-                 * implementation) is that the string table is sorted first by reverse count (ie descending)
-                 * and then by reverse lexicographic order.
-                 */
-                void store_stringtable(OSMPBF::StringTable* st, bool sort) {
-                    // add empty StringTable entry at index 0
-                    // StringTable index 0 is reserved as delimiter in the densenodes key/value list
-                    // this line also ensures that there's always a valid StringTable
-                    st->add_s("");
-
-                    if (sort) {
-                        std::multimap<string_info, std::string> sortedbycount;
-
-                        m_id2id_map.resize(m_size+1);
-
-                        std::transform(m_strings.begin(), m_strings.end(),
-                                    std::inserter(sortedbycount, sortedbycount.begin()),
-                                    [](const std::pair<std::string, string_info>& p) {
-                                            return std::pair<string_info, std::string>(p.second, p.first);
-                                    });
-
-                        string_id_type n = 0;
-
-                        for (const auto& mapping : sortedbycount) {
-                            // add the string of the current item to the pbf StringTable
-                            st->add_s(mapping.second);
-
-                            // store the mapping from the interim-id to the real id
-                            m_id2id_map[mapping.first.interim_id] = ++n;
-                        }
-                    } else {
-                        std::vector<std::pair<string_id_type, const char*>> sortedbyid;
-                        sortedbyid.reserve(m_strings.size());
-
-                        for (const auto& p : m_strings) {
-                            sortedbyid.emplace_back(p.second.interim_id, p.first.c_str());
-                        }
-
-                        std::sort(sortedbyid.begin(), sortedbyid.end());
-                        for (const auto& mapping : sortedbyid) {
-                            st->add_s(mapping.second);
-                        }
-                    }
-                }
-
-                /**
-                 * Map from an interim ID to a real string ID.
-                 */
-                string_id_type map_string_id(const string_id_type interim_id) const {
-                    return m_id2id_map[interim_id];
-                }
-
-                template <typename T>
-                string_id_type map_string_id(const T interim_id) const {
-                    return map_string_id(static_cast_with_assert<string_id_type>(interim_id));
-                }
-
-                /**
-                 * Clear the stringtable, preparing for the next block.
-                 */
-                void clear() {
-                    m_strings.clear();
-                    m_id2id_map.clear();
-                    m_size = 0;
-                }
-
-            }; // class StringTable
-
-        } // namespace detail
-
-    } // namespace io
-
-} // namespace osmium
-
-#endif // OSMIUM_IO_DETAIL_PBF_STRINGTABLE_HPP
diff --git a/include/osmium/io/detail/pbf_type_conv.hpp b/include/osmium/io/detail/pbf_type_conv.hpp
deleted file mode 100644
index 799869a..0000000
--- a/include/osmium/io/detail/pbf_type_conv.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#ifndef OSMIUM_IO_DETAIL_PBF_TYPE_CONV_HPP
-#define OSMIUM_IO_DETAIL_PBF_TYPE_CONV_HPP
-
-/*
-
-This file is part of Osmium (http://osmcode.org/libosmium).
-
-Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-
-*/
-
-#include <osmpbf/osmpbf.h>
-
-#include <osmium/osm/item_type.hpp>
-
-namespace osmium {
-
-// avoid g++ false positive
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wreturn-type"
-    inline item_type osmpbf_membertype_to_item_type(const OSMPBF::Relation::MemberType mt) {
-        switch (mt) {
-            case OSMPBF::Relation::NODE:
-                return item_type::node;
-            case OSMPBF::Relation::WAY:
-                return item_type::way;
-            case OSMPBF::Relation::RELATION:
-                return item_type::relation;
-        }
-    }
-#pragma GCC diagnostic pop
-
-    inline OSMPBF::Relation::MemberType item_type_to_osmpbf_membertype(const item_type type) {
-        switch (type) {
-            case item_type::node:
-                return OSMPBF::Relation::NODE;
-            case item_type::way:
-                return OSMPBF::Relation::WAY;
-            case item_type::relation:
-                return OSMPBF::Relation::RELATION;
-            default:
-                throw std::runtime_error("Unknown relation member type");
-        }
-    }
-
-
-} // namespace osmium
-
-#endif // OSMIUM_IO_DETAIL_PBF_TYPE_CONV_HPP
diff --git a/include/osmium/io/detail/protobuf_tags.hpp b/include/osmium/io/detail/protobuf_tags.hpp
new file mode 100644
index 0000000..3f23087
--- /dev/null
+++ b/include/osmium/io/detail/protobuf_tags.hpp
@@ -0,0 +1,170 @@
+#ifndef OSMIUM_IO_DETAIL_PROTOBUF_TAGS_HPP
+#define OSMIUM_IO_DETAIL_PROTOBUF_TAGS_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <protozero/pbf_types.hpp>
+
+namespace osmium {
+
+    namespace io {
+
+        namespace detail {
+
+            // directly translated from
+            // https://github.com/scrosby/OSM-binary/blob/master/src/fileformat.proto
+
+            namespace FileFormat {
+
+                enum class Blob : protozero::pbf_tag_type {
+                    optional_bytes_raw       = 1,
+                    optional_int32_raw_size  = 2,
+                    optional_bytes_zlib_data = 3,
+                    optional_bytes_lzma_data = 4
+                };
+
+                enum class BlobHeader : protozero::pbf_tag_type {
+                    required_string_type     = 1,
+                    optional_bytes_indexdata = 2,
+                    required_int32_datasize  = 3
+                };
+
+            } // namespace FileFormat
+
+            // directly translated from
+            // https://github.com/scrosby/OSM-binary/blob/master/src/osmformat.proto
+
+            namespace OSMFormat {
+
+                enum class HeaderBlock : protozero::pbf_tag_type {
+                    optional_HeaderBBox_bbox          =  1,
+                    repeated_string_required_features =  4,
+                    repeated_string_optional_features =  5,
+                    optional_string_writingprogram    = 16,
+                    optional_string_source            = 17,
+                    optional_int64_osmosis_replication_timestamp       = 32,
+                    optional_int64_osmosis_replication_sequence_number = 33,
+                    optional_string_osmosis_replication_base_url       = 34
+                };
+
+                enum class HeaderBBox : protozero::pbf_tag_type {
+                    required_sint64_left   = 1,
+                    required_sint64_right  = 2,
+                    required_sint64_top    = 3,
+                    required_sint64_bottom = 4
+                };
+
+                enum class PrimitiveBlock : protozero::pbf_tag_type {
+                    required_StringTable_stringtable       =  1,
+                    repeated_PrimitiveGroup_primitivegroup =  2,
+                    optional_int32_granularity             = 17,
+                    optional_int32_date_granularity        = 18,
+                    optional_int64_lat_offset              = 19,
+                    optional_int64_lon_offset              = 20
+                };
+
+                enum class PrimitiveGroup : protozero::pbf_tag_type {
+                    unknown                       = 0,
+                    repeated_Node_nodes           = 1,
+                    optional_DenseNodes_dense     = 2,
+                    repeated_Way_ways             = 3,
+                    repeated_Relation_relations   = 4,
+                    repeated_ChangeSet_changesets = 5
+                };
+
+                enum class StringTable : protozero::pbf_tag_type {
+                    repeated_bytes_s = 1
+                };
+
+                enum class Info : protozero::pbf_tag_type {
+                    optional_int32_version   = 1,
+                    optional_int64_timestamp = 2,
+                    optional_int64_changeset = 3,
+                    optional_int32_uid       = 4,
+                    optional_uint32_user_sid = 5,
+                    optional_bool_visible    = 6
+                };
+
+                enum class DenseInfo : protozero::pbf_tag_type {
+                    packed_int32_version    = 1,
+                    packed_sint64_timestamp = 2,
+                    packed_sint64_changeset = 3,
+                    packed_sint32_uid       = 4,
+                    packed_sint32_user_sid  = 5,
+                    packed_bool_visible     = 6
+                };
+
+                enum class Node : protozero::pbf_tag_type {
+                    required_sint64_id  = 1,
+                    packed_uint32_keys  = 2,
+                    packed_uint32_vals  = 3,
+                    optional_Info_info  = 4,
+                    required_sint64_lat = 8,
+                    required_sint64_lon = 9
+                };
+
+                enum class DenseNodes : protozero::pbf_tag_type {
+                    packed_sint64_id             =  1,
+                    optional_DenseInfo_denseinfo =  5,
+                    packed_sint64_lat            =  8,
+                    packed_sint64_lon            =  9,
+                    packed_int32_keys_vals       = 10
+                };
+
+                enum class Way : protozero::pbf_tag_type {
+                    required_int64_id  = 1,
+                    packed_uint32_keys = 2,
+                    packed_uint32_vals = 3,
+                    optional_Info_info = 4,
+                    packed_sint64_refs = 8
+                };
+
+                enum class Relation : protozero::pbf_tag_type {
+                    required_int64_id       =  1,
+                    packed_uint32_keys      =  2,
+                    packed_uint32_vals      =  3,
+                    optional_Info_info      =  4,
+                    packed_int32_roles_sid  =  8,
+                    packed_sint64_memids    =  9,
+                    packed_MemberType_types = 10
+                };
+
+            } // namespace OSMFormat
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+#endif //  OSMIUM_IO_DETAIL_PROTOBUF_TAGS_HPP
diff --git a/include/osmium/io/detail/string_table.hpp b/include/osmium/io/detail/string_table.hpp
new file mode 100644
index 0000000..ae9d5f0
--- /dev/null
+++ b/include/osmium/io/detail/string_table.hpp
@@ -0,0 +1,250 @@
+#ifndef OSMIUM_IO_DETAIL_STRING_TABLE_HPP
+#define OSMIUM_IO_DETAIL_STRING_TABLE_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <iterator>
+#include <list>
+#include <map>
+#include <string>
+
+namespace osmium {
+
+    namespace io {
+
+        namespace detail {
+
+            /**
+             * class StringStore
+             *
+             * Storage of lots of strings (const char *). Memory is allocated in chunks.
+             * If a string is added and there is no space in the current chunk, a new
+             * chunk will be allocated. Strings added to the store must not be larger
+             * than the chunk size.
+             *
+             * All memory is released when the destructor is called. There is no other way
+             * to release all or part of the memory.
+             *
+             */
+            class StringStore {
+
+                size_t m_chunk_size;
+
+                std::list<std::string> m_chunks;
+
+                void add_chunk() {
+                    m_chunks.push_front(std::string());
+                    m_chunks.front().reserve(m_chunk_size);
+                }
+
+            public:
+
+                StringStore(size_t chunk_size) :
+                    m_chunk_size(chunk_size),
+                    m_chunks() {
+                    add_chunk();
+                }
+
+                void clear() noexcept {
+                    m_chunks.erase(std::next(m_chunks.begin()), m_chunks.end());
+                    m_chunks.front().clear();
+                }
+
+                /**
+                 * Add a null terminated string to the store. This will
+                 * automatically get more memory if we are out.
+                 * Returns a pointer to the copy of the string we have
+                 * allocated.
+                 */
+                const char* add(const char* string) {
+                    size_t len = std::strlen(string) + 1;
+
+                    assert(len <= m_chunk_size);
+
+                    size_t chunk_len = m_chunks.front().size();
+                    if (chunk_len + len > m_chunks.front().capacity()) {
+                        add_chunk();
+                        chunk_len = 0;
+                    }
+
+                    m_chunks.front().append(string);
+                    m_chunks.front().append(1, '\0');
+
+                    return m_chunks.front().c_str() + chunk_len;
+                }
+
+                class const_iterator : public std::iterator<std::forward_iterator_tag, const char*> {
+
+                    typedef std::list<std::string>::const_iterator it_type;
+                    it_type m_it;
+                    const it_type m_last;
+                    const char* m_pos;
+
+                public:
+
+                    const_iterator(it_type it, it_type last) :
+                        m_it(it),
+                        m_last(last),
+                        m_pos(it == last ? nullptr : m_it->c_str()) {
+                    }
+
+                    const_iterator& operator++() {
+                        assert(m_it != m_last);
+                        auto last_pos = m_it->c_str() + m_it->size();
+                        while (m_pos != last_pos && *m_pos) ++m_pos;
+                        if (m_pos != last_pos) ++m_pos;
+                        if (m_pos == last_pos) {
+                            ++m_it;
+                            if (m_it != m_last) {
+                                m_pos = m_it->c_str();
+                            } else {
+                                m_pos = nullptr;
+                            }
+                        }
+                        return *this;
+                    }
+
+                    const_iterator operator++(int) {
+                        const_iterator tmp(*this);
+                        operator++();
+                        return tmp;
+                    }
+
+                    bool operator==(const const_iterator& rhs) const {
+                        return m_it == rhs.m_it && m_pos == rhs.m_pos;
+                    }
+
+                    bool operator!=(const const_iterator& rhs) const {
+                        return !(*this == rhs);
+                    }
+
+                    const char* operator*() const {
+                        assert(m_it != m_last);
+                        assert(m_pos != nullptr);
+                        return m_pos;
+                    }
+
+                }; // class const_iterator
+
+                const_iterator begin() const {
+                    if (m_chunks.front().empty()) {
+                        return end();
+                    }
+                    return const_iterator(m_chunks.begin(), m_chunks.end());
+                }
+
+                const_iterator end() const {
+                    return const_iterator(m_chunks.end(), m_chunks.end());
+                }
+
+                // These functions get you some idea how much memory was
+                // used.
+                int get_chunk_size() const noexcept {
+                    return m_chunk_size;
+                }
+
+                int get_chunk_count() const noexcept {
+                    return m_chunks.size();
+                }
+
+                int get_used_bytes_in_last_chunk() const noexcept {
+                    return m_chunks.front().size();
+                }
+
+            }; // class StringStore
+
+            struct StrComp {
+
+                bool operator()(const char* lhs, const char* rhs) const {
+                    return strcmp(lhs, rhs) < 0;
+                }
+
+            }; // struct StrComp
+
+            class StringTable {
+
+                StringStore m_strings;
+                std::map<const char*, size_t, StrComp> m_index;
+                size_t m_size;
+
+            public:
+
+                StringTable() :
+                    m_strings(1024 * 1024),
+                    m_index(),
+                    m_size(0) {
+                    m_strings.add("");
+                }
+
+                void clear() {
+                    m_strings.clear();
+                    m_index.clear();
+                    m_size = 0;
+                    m_strings.add("");
+                }
+
+                size_t size() const noexcept {
+                    return m_size + 1;
+                }
+
+                size_t add(const char* s) {
+                    auto f = m_index.find(s);
+                    if (f != m_index.end()) {
+                        return f->second;
+                    }
+
+                    const char* cs = m_strings.add(s);
+                    m_index[cs] = ++m_size;
+                    return m_size;
+                }
+
+                StringStore::const_iterator begin() const {
+                    return m_strings.begin();
+                }
+
+                StringStore::const_iterator end() const {
+                    return m_strings.end();
+                }
+
+            }; // class StringTable
+
+        } // namespace detail
+
+    } // namespace io
+
+} // namespace osmium
+
+#endif // OSMIUM_IO_DETAIL_STRING_TABLE_HPP
diff --git a/include/osmium/io/detail/xml_input_format.hpp b/include/osmium/io/detail/xml_input_format.hpp
index a1c6666..b0f3da3 100644
--- a/include/osmium/io/detail/xml_input_format.hpp
+++ b/include/osmium/io/detail/xml_input_format.hpp
@@ -192,6 +192,8 @@ namespace osmium {
 
                 std::atomic<bool>& m_done;
 
+                bool m_header_is_done;
+
                 /**
                  * A C++ wrapper for the Expat parser that makes sure no memory is leaked.
                  */
@@ -247,16 +249,25 @@ namespace osmium {
 
                     T& m_data;
                     std::promise<T>& m_promise;
+                    bool m_done;
 
                 public:
 
                     PromiseKeeper(T& data, std::promise<T>& promise) :
                         m_data(data),
-                        m_promise(promise) {
+                        m_promise(promise),
+                        m_done(false) {
+                    }
+
+                    void fullfill_promise() {
+                        if (!m_done) {
+                            m_promise.set_value(m_data);
+                            m_done = true;
+                        }
                     }
 
                     ~PromiseKeeper() {
-                        m_promise.set_value(m_data);
+                        fullfill_promise();
                     }
 
                 }; // class PromiseKeeper
@@ -280,7 +291,8 @@ namespace osmium {
                     m_queue(queue),
                     m_header_promise(header_promise),
                     m_read_types(read_types),
-                    m_done(done) {
+                    m_done(done),
+                    m_header_is_done(false) {
                 }
 
                 /**
@@ -306,7 +318,8 @@ namespace osmium {
                     m_queue(other.m_queue),
                     m_header_promise(other.m_header_promise),
                     m_read_types(other.m_read_types),
-                    m_done(other.m_done) {
+                    m_done(other.m_done),
+                    m_header_is_done(other.m_header_is_done) {
                 }
 
                 XMLParser(XMLParser&&) = default;
@@ -327,6 +340,9 @@ namespace osmium {
                         last = data.empty();
                         try {
                             parser(data, last);
+                            if (m_header_is_done) {
+                                promise_keeper.fullfill_promise();
+                            }
                         } catch (ParserIsDone&) {
                             return true;
                         } catch (...) {
@@ -344,8 +360,7 @@ namespace osmium {
             private:
 
                 const char* init_object(osmium::OSMObject& object, const XML_Char** attrs) {
-                    static const char* empty = "";
-                    const char* user = empty;
+                    const char* user = "";
 
                     if (m_in_delete_section) {
                         object.set_visible(false);
@@ -372,8 +387,7 @@ namespace osmium {
                 }
 
                 void init_changeset(osmium::builder::ChangesetBuilder* builder, const XML_Char** attrs) {
-                    static const char* empty = "";
-                    const char* user = empty;
+                    const char* user = "";
                     osmium::Changeset& new_changeset = builder->object();
 
                     osmium::Location min;
@@ -422,6 +436,7 @@ namespace osmium {
                 }
 
                 void header_is_done() {
+                    m_header_is_done = true;
                     if (m_read_types == osmium::osm_entity_bits::nothing) {
                         throw ParserIsDone();
                     }
@@ -723,10 +738,15 @@ namespace osmium {
 
             namespace {
 
+// we want the register_input_format() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
                 const bool registered_xml_input = osmium::io::detail::InputFormatFactory::instance().register_input_format(osmium::io::file_format::xml,
                     [](const osmium::io::File& file, osmium::osm_entity_bits::type read_which_entities, osmium::thread::Queue<std::string>& input_queue) {
                         return new osmium::io::detail::XMLInputFormat(file, read_which_entities, input_queue);
                 });
+#pragma GCC diagnostic pop
 
             } // anonymous namespace
 
diff --git a/include/osmium/io/detail/xml_output_format.hpp b/include/osmium/io/detail/xml_output_format.hpp
index 65ba171..2a381d5 100644
--- a/include/osmium/io/detail/xml_output_format.hpp
+++ b/include/osmium/io/detail/xml_output_format.hpp
@@ -85,6 +85,9 @@ namespace osmium {
                             case '\'': out += "'"; break;
                             case '<':  out += "<";   break;
                             case '>':  out += ">";   break;
+                            case '\n': out += "&#xA;";  break;
+                            case '\r': out += "&#xD;";  break;
+                            case '\t': out += "&#x9;";  break;
                             default:   out += *in;      break;
                         }
                     }
@@ -126,6 +129,7 @@ namespace osmium {
 
                 operation m_last_op {operation::op_none};
 
+                const bool m_add_metadata;
                 const bool m_write_visible_flag;
                 const bool m_write_change_ops;
 
@@ -146,31 +150,33 @@ namespace osmium {
                 void write_meta(const osmium::OSMObject& object) {
                     oprintf(*m_out, " id=\"%" PRId64 "\"", object.id());
 
-                    if (object.version()) {
-                        oprintf(*m_out, " version=\"%d\"", object.version());
-                    }
+                    if (m_add_metadata) {
+                        if (object.version()) {
+                            oprintf(*m_out, " version=\"%d\"", object.version());
+                        }
 
-                    if (object.timestamp()) {
-                        *m_out += " timestamp=\"";
-                        *m_out += object.timestamp().to_iso();
-                        *m_out += "\"";
-                    }
+                        if (object.timestamp()) {
+                            *m_out += " timestamp=\"";
+                            *m_out += object.timestamp().to_iso();
+                            *m_out += "\"";
+                        }
 
-                    if (!object.user_is_anonymous()) {
-                        oprintf(*m_out, " uid=\"%d\" user=\"", object.uid());
-                        xml_string(*m_out, object.user());
-                        *m_out += "\"";
-                    }
+                        if (!object.user_is_anonymous()) {
+                            oprintf(*m_out, " uid=\"%d\" user=\"", object.uid());
+                            xml_string(*m_out, object.user());
+                            *m_out += "\"";
+                        }
 
-                    if (object.changeset()) {
-                        oprintf(*m_out, " changeset=\"%d\"", object.changeset());
-                    }
+                        if (object.changeset()) {
+                            oprintf(*m_out, " changeset=\"%d\"", object.changeset());
+                        }
 
-                    if (m_write_visible_flag) {
-                        if (object.visible()) {
-                            *m_out += " visible=\"true\"";
-                        } else {
-                            *m_out += " visible=\"false\"";
+                        if (m_write_visible_flag) {
+                            if (object.visible()) {
+                                *m_out += " visible=\"true\"";
+                            } else {
+                                *m_out += " visible=\"false\"";
+                            }
                         }
                     }
                 }
@@ -224,9 +230,10 @@ namespace osmium {
 
             public:
 
-                explicit XMLOutputBlock(osmium::memory::Buffer&& buffer, bool write_visible_flag, bool write_change_ops) :
+                explicit XMLOutputBlock(osmium::memory::Buffer&& buffer, bool add_metadata, bool write_visible_flag, bool write_change_ops) :
                     m_input_buffer(std::make_shared<osmium::memory::Buffer>(std::move(buffer))),
                     m_out(std::make_shared<std::string>()),
+                    m_add_metadata(add_metadata),
                     m_write_visible_flag(write_visible_flag && !write_change_ops),
                     m_write_change_ops(write_change_ops) {
                 }
@@ -392,12 +399,14 @@ namespace osmium {
 
             class XMLOutputFormat : public osmium::io::detail::OutputFormat, public osmium::handler::Handler {
 
+                bool m_add_metadata;
                 bool m_write_visible_flag;
 
             public:
 
                 XMLOutputFormat(const osmium::io::File& file, data_queue_type& output_queue) :
                     OutputFormat(file, output_queue),
+                    m_add_metadata(file.get("add_metadata") != "false"),
                     m_write_visible_flag(file.has_multiple_object_versions() || m_file.is_true("force_visible_flag")) {
                 }
 
@@ -408,7 +417,7 @@ namespace osmium {
                 }
 
                 void write_buffer(osmium::memory::Buffer&& buffer) override final {
-                    m_output_queue.push(osmium::thread::Pool::instance().submit(XMLOutputBlock{std::move(buffer), m_write_visible_flag, m_file.is_true("xml_change_format")}));
+                    m_output_queue.push(osmium::thread::Pool::instance().submit(XMLOutputBlock{std::move(buffer), m_add_metadata, m_write_visible_flag, m_file.is_true("xml_change_format")}));
                 }
 
                 void write_header(const osmium::io::Header& header) override final {
@@ -468,10 +477,15 @@ namespace osmium {
 
             namespace {
 
+// we want the register_output_format() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
                 const bool registered_xml_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::xml,
                     [](const osmium::io::File& file, data_queue_type& output_queue) {
                         return new osmium::io::detail::XMLOutputFormat(file, output_queue);
                 });
+#pragma GCC diagnostic pop
 
             } // anonymous namespace
 
diff --git a/include/osmium/io/detail/zlib.hpp b/include/osmium/io/detail/zlib.hpp
index a402bf7..fc0baf3 100644
--- a/include/osmium/io/detail/zlib.hpp
+++ b/include/osmium/io/detail/zlib.hpp
@@ -85,23 +85,24 @@ namespace osmium {
              *
              * @param input Compressed input data.
              * @param raw_size Size of uncompressed data.
-             * @returns Uncompressed data.
+             * @param output Uncompressed result data.
+             * @returns Pointer and size to incompressed data.
              */
-            inline std::unique_ptr<std::string> zlib_uncompress(const std::string& input, unsigned long raw_size) {
-                auto output = std::unique_ptr<std::string>(new std::string(raw_size, '\0'));
+            inline std::pair<const char*, size_t> zlib_uncompress_string(const char* input, unsigned long input_size, unsigned long raw_size, std::string& output) {
+                output.resize(raw_size);
 
                 auto result = ::uncompress(
-                    reinterpret_cast<unsigned char*>(const_cast<char *>(output->data())),
+                    reinterpret_cast<unsigned char*>(&*output.begin()),
                     &raw_size,
-                    reinterpret_cast<const unsigned char*>(input.data()),
-                    osmium::static_cast_with_assert<unsigned long>(input.size())
+                    reinterpret_cast<const unsigned char*>(input),
+                    input_size
                 );
 
                 if (result != Z_OK) {
                     throw std::runtime_error(std::string("failed to uncompress data: ") + zError(result));
                 }
 
-                return output;
+                return std::make_pair(output.data(), output.size());
             }
 
         } // namespace detail
diff --git a/include/osmium/io/file.hpp b/include/osmium/io/file.hpp
index 5b6c02f..3bbfacc 100644
--- a/include/osmium/io/file.hpp
+++ b/include/osmium/io/file.hpp
@@ -97,7 +97,9 @@ namespace osmium {
              *                 of the file will be taken from the suffix.
              *                 An empty filename or "-" means stdin or stdout.
              * @param format File format as string. See the description of the
-             *               parse_format() function for details.
+             *               parse_format() function for details. If this is
+             *               empty the format will be deduced from the suffix
+             *               of the filename.
              */
             explicit File(const std::string& filename = "", const std::string& format = "") :
                 Options(),
@@ -107,20 +109,19 @@ namespace osmium {
                 m_format_string(format) {
 
                 // stdin/stdout
-                if (filename == "" || filename == "-") {
+                if (m_filename == "-") {
                     m_filename = "";
-                    default_settings_for_stdinout();
                 }
 
-                // filename is actually a URL
+                // if filename is a URL, default to XML format
                 std::string protocol = m_filename.substr(0, m_filename.find_first_of(':'));
                 if (protocol == "http" || protocol == "https") {
-                    default_settings_for_url();
+                    m_file_format = file_format::xml;
                 }
 
-                detect_format_from_suffix(m_filename);
-
-                if (format != "") {
+                if (format.empty()) {
+                    detect_format_from_suffix(m_filename);
+                } else {
                     parse_format(format);
                 }
             }
@@ -140,9 +141,6 @@ namespace osmium {
                 m_buffer(buffer),
                 m_buffer_size(size),
                 m_format_string(format) {
-
-                default_settings_for_stdinout();
-
                 if (format != "") {
                     parse_format(format);
                 }
@@ -220,6 +218,20 @@ namespace osmium {
                 } else if (suffixes.back() == "opl") {
                     m_file_format = file_format::opl;
                     suffixes.pop_back();
+                } else if (suffixes.back() == "json") {
+                    m_file_format = file_format::json;
+                    suffixes.pop_back();
+                } else if (suffixes.back() == "o5m") {
+                    m_file_format = file_format::o5m;
+                    suffixes.pop_back();
+                } else if (suffixes.back() == "o5c") {
+                    m_file_format = file_format::o5m;
+                    m_has_multiple_object_versions = true;
+                    set("o5c_change_format", true);
+                    suffixes.pop_back();
+                } else if (suffixes.back() == "debug") {
+                    m_file_format = file_format::debug;
+                    suffixes.pop_back();
                 }
 
                 if (suffixes.empty()) return;
@@ -240,8 +252,8 @@ namespace osmium {
             }
 
             /**
-             * Check file format etc. for consistency and throw exception if there
-             * is a problem.
+             * Check file format etc. for consistency and throw exception if
+             * there is a problem.
              *
              * @throws std::runtime_error
              */
@@ -265,36 +277,6 @@ namespace osmium {
                 }
             }
 
-            /**
-             * Set default settings for type and encoding when the filename is
-             * empty or "-". If you want to have a different default setting
-             * override this in a subclass.
-             */
-            void default_settings_for_stdinout() {
-                m_file_format      = file_format::unknown;
-                m_file_compression = file_compression::none;
-            }
-
-            /**
-             * Set default settings for type and encoding when the filename is
-             * a normal file. If you want to have a different default setting
-             * override this in a subclass.
-             */
-            void default_settings_for_file() {
-                m_file_format      = file_format::unknown;
-                m_file_compression = file_compression::none;
-            }
-
-            /**
-             * Set default settings for type and encoding when the filename is a URL.
-             * If you want to have a different default setting override this in a
-             * subclass.
-             */
-            void default_settings_for_url() {
-                m_file_format      = file_format::xml;
-                m_file_compression = file_compression::none;
-            }
-
             file_format format() const noexcept {
                 return m_file_format;
             }
diff --git a/include/osmium/io/file_format.hpp b/include/osmium/io/file_format.hpp
index 1a63a5e..edfb1ff 100644
--- a/include/osmium/io/file_format.hpp
+++ b/include/osmium/io/file_format.hpp
@@ -44,7 +44,9 @@ namespace osmium {
             xml     = 1,
             pbf     = 2,
             opl     = 3,
-            json    = 4
+            json    = 4,
+            o5m     = 5,
+            debug   = 6
         };
 
 // avoid g++ false positive
@@ -62,6 +64,10 @@ namespace osmium {
                     return "OPL";
                 case file_format::json:
                     return "JSON";
+                case file_format::o5m:
+                    return "O5M";
+                case file_format::debug:
+                    return "DEBUG";
             }
         }
 #pragma GCC diagnostic pop
diff --git a/include/osmium/io/gzip_compression.hpp b/include/osmium/io/gzip_compression.hpp
index 2723977..204bfd5 100644
--- a/include/osmium/io/gzip_compression.hpp
+++ b/include/osmium/io/gzip_compression.hpp
@@ -231,11 +231,16 @@ namespace osmium {
 
         namespace {
 
+// we want the register_compression() function to run, setting the variable
+// is only a side-effect, it will never be used
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
             const bool registered_gzip_compression = osmium::io::CompressionFactory::instance().register_compression(osmium::io::file_compression::gzip,
                 [](int fd) { return new osmium::io::GzipCompressor(fd); },
                 [](int fd) { return new osmium::io::GzipDecompressor(fd); },
                 [](const char* buffer, size_t size) { return new osmium::io::GzipBufferDecompressor(buffer, size); }
             );
+#pragma GCC diagnostic pop
 
         } // anonymous namespace
 
diff --git a/include/osmium/io/pbf_input.hpp b/include/osmium/io/pbf_input.hpp
index 766153e..d7f3787 100644
--- a/include/osmium/io/pbf_input.hpp
+++ b/include/osmium/io/pbf_input.hpp
@@ -39,7 +39,6 @@ DEALINGS IN THE SOFTWARE.
  * Include this file if you want to read OSM PBF files.
  *
  * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
  *            `libz`, and enable multithreading.
  */
 
diff --git a/include/osmium/io/pbf_output.hpp b/include/osmium/io/pbf_output.hpp
index 5f46ede..dad1013 100644
--- a/include/osmium/io/pbf_output.hpp
+++ b/include/osmium/io/pbf_output.hpp
@@ -39,7 +39,6 @@ DEALINGS IN THE SOFTWARE.
  * Include this file if you want to write OSM PBF files.
  *
  * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
  *            `libz`, and enable multithreading.
  */
 
diff --git a/include/osmium/memory/buffer.hpp b/include/osmium/memory/buffer.hpp
index ecf8f33..d800c68 100644
--- a/include/osmium/memory/buffer.hpp
+++ b/include/osmium/memory/buffer.hpp
@@ -37,7 +37,6 @@ DEALINGS IN THE SOFTWARE.
 #include <cassert>
 #include <cstddef>
 #include <cstring>
-#include <exception>
 #include <functional>
 #include <iterator>
 #include <stdexcept>
@@ -83,7 +82,7 @@ namespace osmium {
          * Buffers exist in two flavours, those with external memory management and
          * those with internal memory management. If you already have some memory
          * with data in it (for instance read from disk), you create a Buffer with
-         * external memory managment. It is your job then to free the memory once
+         * external memory management. It is your job then to free the memory once
          * the buffer isn't used any more. If you don't have memory already, you can
          * create a Buffer object and have it manage the memory internally. It will
          * dynamically allocate memory and free it again after use.
diff --git a/include/osmium/memory/collection.hpp b/include/osmium/memory/collection.hpp
index 7deb88b..5cf3cc6 100644
--- a/include/osmium/memory/collection.hpp
+++ b/include/osmium/memory/collection.hpp
@@ -38,7 +38,6 @@ DEALINGS IN THE SOFTWARE.
 #include <type_traits>
 
 #include <osmium/memory/item.hpp>
-#include <osmium/util/compatibility.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/memory/item.hpp b/include/osmium/memory/item.hpp
index 2679ca6..dc54404 100644
--- a/include/osmium/memory/item.hpp
+++ b/include/osmium/memory/item.hpp
@@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstddef>
 #include <cstdint>
 #include <type_traits>
 
diff --git a/include/osmium/osm/changeset.hpp b/include/osmium/osm/changeset.hpp
index 20def70..07bc0dd 100644
--- a/include/osmium/osm/changeset.hpp
+++ b/include/osmium/osm/changeset.hpp
@@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <cstdint>
 #include <cstring>
 
 #include <osmium/memory/collection.hpp>
diff --git a/include/osmium/osm/crc.hpp b/include/osmium/osm/crc.hpp
new file mode 100644
index 0000000..eefa4a1
--- /dev/null
+++ b/include/osmium/osm/crc.hpp
@@ -0,0 +1,223 @@
+#ifndef OSMIUM_OSM_CRC_HPP
+#define OSMIUM_OSM_CRC_HPP
+
+/*
+
+This file is part of Osmium (http://osmcode.org/libosmium).
+
+Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include <cstdint>
+
+#include <osmium/osm/area.hpp>
+#include <osmium/osm/changeset.hpp>
+#include <osmium/osm/location.hpp>
+#include <osmium/osm/node.hpp>
+#include <osmium/osm/node_ref_list.hpp>
+#include <osmium/osm/relation.hpp>
+#include <osmium/osm/way.hpp>
+#include <osmium/util/endian.hpp>
+
+namespace osmium {
+
+    template <class TCRC>
+    class CRC {
+
+        static inline uint16_t byte_swap_16(uint16_t value) noexcept {
+# if defined(__GNUC__) || defined(__clang__)
+            return __builtin_bswap16(value);
+# else
+            return (value >> 8) | (value << 8);
+# endif
+        }
+
+        static inline uint32_t byte_swap_32(uint32_t value) noexcept {
+# if defined(__GNUC__) || defined(__clang__)
+            return __builtin_bswap32(value);
+# else
+            return  (value >> 24) |
+                    ((value >>  8) & 0x0000FF00) |
+                    ((value <<  8) & 0x00FF0000) |
+                    (value << 24);
+# endif
+        }
+
+        static inline uint64_t byte_swap_64(uint64_t value) noexcept {
+# if defined(__GNUC__) || defined(__clang__)
+            return __builtin_bswap64(value);
+# else
+            uint64_t val1 = byte_swap_32(value & 0xFFFFFFFF);
+            uint64_t val2 = byte_swap_32(value >> 32);
+            return (val1 << 32) & val2;
+# endif
+        }
+
+        TCRC m_crc;
+
+    public:
+
+        TCRC& operator()() {
+            return m_crc;
+        }
+
+        const TCRC& operator()() const {
+            return m_crc;
+        }
+
+        void update_bool(bool value) {
+            m_crc.process_byte(value);
+        }
+
+        void update_int8(uint8_t value) {
+            m_crc.process_byte(value);
+        }
+
+        void update_int16(uint16_t value) {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+            m_crc.process_bytes(&value, sizeof(uint16_t));
+#else
+            uint16_t v = byte_swap_16(value);
+            m_crc.process_bytes(&v, sizeof(uint16_t));
+#endif
+        }
+
+        void update_int32(uint32_t value) {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+            m_crc.process_bytes(&value, sizeof(uint32_t));
+#else
+            uint32_t v = byte_swap_32(value);
+            m_crc.process_bytes(&v, sizeof(uint32_t));
+#endif
+        }
+
+        void update_int64(uint64_t value) {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+            m_crc.process_bytes(&value, sizeof(uint64_t));
+#else
+            uint64_t v = byte_swap_64(value);
+            m_crc.process_bytes(&v, sizeof(uint64_t));
+#endif
+        }
+
+        void update_string(const char* str) {
+            while (*str) {
+                m_crc.process_byte(*str++);
+            }
+        }
+
+        void update(const Timestamp& timestamp) {
+            update_int32(uint32_t(timestamp));
+        }
+
+        void update(const osmium::Location& location) {
+            update_int32(location.x());
+            update_int32(location.y());
+        }
+
+        void update(const osmium::Box& box) {
+            update(box.bottom_left());
+            update(box.top_right());
+        }
+
+        void update(const NodeRef& node_ref) {
+            update_int64(node_ref.ref());
+        }
+
+        void update(const NodeRefList& node_refs) {
+            for (const NodeRef& node_ref : node_refs) {
+                update(node_ref);
+            }
+        }
+
+        void update(const TagList& tags) {
+            m_crc.process_bytes(tags.data(), tags.byte_size());
+        }
+
+        void update(const osmium::RelationMember& member) {
+            update_int64(member.ref());
+            update_int16(uint16_t(member.type()));
+            update_string(member.role());
+        }
+
+        void update(const osmium::RelationMemberList& members) {
+            for (const RelationMember& member : members) {
+                update(member);
+            }
+        }
+
+        void update(const osmium::OSMObject& object) {
+            update_int64(object.id());
+            update_bool(object.visible());
+            update_int32(object.version());
+            update(object.timestamp());
+            update_int32(object.uid());
+            update_string(object.user());
+            update(object.tags());
+        }
+
+        void update(const osmium::Node& node) {
+            update(static_cast<const osmium::OSMObject&>(node));
+            update(node.location());
+        }
+
+        void update(const osmium::Way& way) {
+            update(static_cast<const osmium::OSMObject&>(way));
+            update(way.nodes());
+        }
+
+        void update(const osmium::Relation& relation) {
+            update(static_cast<const osmium::OSMObject&>(relation));
+            update(relation.members());
+        }
+
+        void update(const osmium::Area& area) {
+            update(static_cast<const osmium::OSMObject&>(area));
+            for (auto it = area.cbegin(); it != area.cend(); ++it) {
+                if (it->type() == osmium::item_type::outer_ring ||
+                    it->type() == osmium::item_type::inner_ring) {
+                    update(static_cast<const osmium::NodeRefList&>(*it));
+                }
+            }
+        }
+
+        void update(const osmium::Changeset& changeset) {
+            update_int64(changeset.id());
+            update(changeset.created_at());
+            update(changeset.closed_at());
+            update(changeset.bounds());
+            update_int32(changeset.num_changes());
+            update_int32(changeset.uid());
+            update_string(changeset.user());
+        }
+
+    }; // class CRC
+
+} // namespace osmium
+
+#endif // OSMIUM_OSM_CRC
diff --git a/include/osmium/osm/entity.hpp b/include/osmium/osm/entity.hpp
index 14861a2..ce292c8 100644
--- a/include/osmium/osm/entity.hpp
+++ b/include/osmium/osm/entity.hpp
@@ -35,6 +35,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <osmium/memory/item.hpp>
 #include <osmium/osm/entity_bits.hpp>
+#include <osmium/osm/item_type.hpp>
 
 namespace osmium {
 
diff --git a/include/osmium/osm/node_ref.hpp b/include/osmium/osm/node_ref.hpp
index 76afa75..72359cd 100644
--- a/include/osmium/osm/node_ref.hpp
+++ b/include/osmium/osm/node_ref.hpp
@@ -33,11 +33,11 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <cstdint>
 #include <cstdlib>
 #include <iosfwd>
 
 #include <osmium/memory/item.hpp>
-#include <osmium/osm/item_type.hpp>
 #include <osmium/osm/location.hpp>
 #include <osmium/osm/types.hpp>
 
diff --git a/include/osmium/osm/timestamp.hpp b/include/osmium/osm/timestamp.hpp
index e4c6807..390f0e7 100644
--- a/include/osmium/osm/timestamp.hpp
+++ b/include/osmium/osm/timestamp.hpp
@@ -39,10 +39,9 @@ DEALINGS IN THE SOFTWARE.
 #include <limits>
 #include <stdexcept>
 #include <string>
-#include <time.h>
 
 #include <osmium/util/compatibility.hpp>
-#include <osmium/util/minmax.hpp>
+#include <osmium/util/minmax.hpp> // IWYU pragma: keep
 
 namespace osmium {
 
diff --git a/include/osmium/thread/queue.hpp b/include/osmium/thread/queue.hpp
index 7fa6469..76ad9a0 100644
--- a/include/osmium/thread/queue.hpp
+++ b/include/osmium/thread/queue.hpp
@@ -41,9 +41,7 @@ DEALINGS IN THE SOFTWARE.
 #include <queue>
 #include <string>
 #include <thread>
-#include <utility>
-
-#include <osmium/util/compatibility.hpp>
+#include <utility> // IWYU pragma: keep (for std::move)
 
 namespace osmium {
 
diff --git a/include/osmium/thread/util.hpp b/include/osmium/thread/util.hpp
index 62bb82a..ca4f6dd 100644
--- a/include/osmium/thread/util.hpp
+++ b/include/osmium/thread/util.hpp
@@ -58,7 +58,7 @@ namespace osmium {
 
         /**
          * Wait until the given future becomes ready. Will block if the future
-         * is not ready. Can be called more than once unless future.get().
+         * is not ready. Can be called more than once unlike future.get().
          */
         template <class T>
         inline void wait_until_done(std::future<T>& future) {
diff --git a/include/osmium/util/data_file.hpp b/include/osmium/util/data_file.hpp
index 3194988..22bf191 100644
--- a/include/osmium/util/data_file.hpp
+++ b/include/osmium/util/data_file.hpp
@@ -34,7 +34,9 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cerrno>
+#include <cstddef>
 #include <cstdio>
+#include <stdexcept>
 #include <string>
 #include <system_error>
 
diff --git a/include/osmium/util/delta.hpp b/include/osmium/util/delta.hpp
index dd733ce..0c77e52 100644
--- a/include/osmium/util/delta.hpp
+++ b/include/osmium/util/delta.hpp
@@ -33,6 +33,8 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
+#include <iterator>
+#include <type_traits>
 #include <utility>
 
 namespace osmium {
@@ -49,8 +51,8 @@ namespace osmium {
 
         public:
 
-            DeltaEncode() :
-                m_value(0) {
+            DeltaEncode(T value = 0) :
+                m_value(value) {
             }
 
             void clear() {
@@ -90,6 +92,54 @@ namespace osmium {
 
         }; // class DeltaDecode
 
+        template <typename TBaseIterator, typename TTransform, typename TValue>
+        class DeltaEncodeIterator : public std::iterator<std::input_iterator_tag, TValue> {
+
+            typedef TValue value_type;
+
+            TBaseIterator m_it;
+            TBaseIterator m_end;
+            value_type m_delta;
+            DeltaEncode<value_type> m_value;
+            TTransform m_trans;
+
+        public:
+
+            DeltaEncodeIterator(TBaseIterator first, TBaseIterator last, TTransform& trans) :
+                m_it(first),
+                m_end(last),
+                m_delta(m_trans(m_it)),
+                m_value(m_delta),
+                m_trans(trans) {
+            }
+
+            DeltaEncodeIterator& operator++() {
+                if (m_it != m_end) {
+                    m_delta = m_value.update(m_trans(++m_it));
+                }
+                return *this;
+            }
+
+            DeltaEncodeIterator operator++(int) {
+                DeltaEncodeIterator tmp(*this);
+                operator++();
+                return tmp;
+            }
+
+            value_type operator*() {
+                return m_delta;
+            }
+
+            bool operator==(const DeltaEncodeIterator& rhs) const {
+                return m_it == rhs.m_it && m_end == rhs.m_end;
+            }
+
+            bool operator!=(const DeltaEncodeIterator& rhs) const {
+                return !(*this == rhs);
+            }
+
+        }; // class DeltaEncodeIterator
+
     } // namespace util
 
 } // namespace osmium
diff --git a/include/osmium/io/pbf_input.hpp b/include/osmium/util/endian.hpp
similarity index 75%
copy from include/osmium/io/pbf_input.hpp
copy to include/osmium/util/endian.hpp
index 766153e..a5d9154 100644
--- a/include/osmium/io/pbf_input.hpp
+++ b/include/osmium/util/endian.hpp
@@ -1,5 +1,5 @@
-#ifndef OSMIUM_IO_PBF_INPUT_HPP
-#define OSMIUM_IO_PBF_INPUT_HPP
+#ifndef OSMIUM_UTIL_ENDIAN_HPP
+#define OSMIUM_UTIL_ENDIAN_HPP
 
 /*
 
@@ -33,17 +33,13 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-/**
- * @file
- *
- * Include this file if you want to read OSM PBF files.
- *
- * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
- *            `libz`, and enable multithreading.
- */
+// Windows is only available for little endian architectures
+// http://stackoverflow.com/questions/6449468/can-i-safely-assume-that-windows-installations-will-always-be-little-endian
+#if !defined(_WIN32) && !defined(__APPLE__)
+# include <endian.h>
+#else
+# define __LITTLE_ENDIAN 1234
+# define __BYTE_ORDER __LITTLE_ENDIAN
+#endif
 
-#include <osmium/io/reader.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_input_format.hpp> // IWYU pragma: export
-
-#endif // OSMIUM_IO_PBF_INPUT_HPP
+#endif // OSMIUM_UTIL_ENDIAN_HPP
diff --git a/include/osmium/util/file.hpp b/include/osmium/util/file.hpp
index afc595b..461f4e6 100644
--- a/include/osmium/util/file.hpp
+++ b/include/osmium/util/file.hpp
@@ -34,6 +34,7 @@ DEALINGS IN THE SOFTWARE.
 */
 
 #include <cerrno>
+#include <cstddef>
 #include <cstdio>
 #include <system_error>
 #include <sys/stat.h>
diff --git a/include/osmium/util/memory_mapping.hpp b/include/osmium/util/memory_mapping.hpp
index 3880000..e48aff2 100644
--- a/include/osmium/util/memory_mapping.hpp
+++ b/include/osmium/util/memory_mapping.hpp
@@ -66,7 +66,7 @@ namespace osmium {
          * @code
          * int fd = ::open(...);
          * {
-         *     MemoryMapping mapping(1024, true, fd, offset);
+         *     MemoryMapping mapping(1024, MemoryMapping::mapping_mode::write_shared, fd, offset);
          *     // use mapping
          * }
          * ::close(fd);
@@ -88,6 +88,15 @@ namespace osmium {
          */
         class MemoryMapping {
 
+public:
+            enum class mapping_mode {
+                readonly      = 0,
+                write_private = 1,
+                write_shared  = 2
+            };
+
+private:
+
             /// The size of the mapping
             size_t m_size;
 
@@ -97,8 +106,8 @@ namespace osmium {
             /// File handle we got the mapping from
             int m_fd;
 
-            /// Is the memory writable?
-            bool m_writable;
+            /// Mapping mode
+            mapping_mode m_mapping_mode;
 
 #ifdef _WIN32
             HANDLE m_handle;
@@ -160,15 +169,20 @@ namespace osmium {
              * created, otherwise a mapping based on the file descriptor will
              * be created.
              *
-             * @pre size > 0 or writable==true
+             * @pre size > 0 or mode == write_shared oder write_private
              *
              * @param size Size of the mapping in bytes
-             * @param writable Should the mapping be writable?
+             * @param mode Mapping mode: readonly, or writable (shared or private)
              * @param fd Open file descriptor of a file we want to map
              * @param offset Offset into the file where the mapping should start
              * @throws std::system_error if the mapping fails
              */
-            MemoryMapping(size_t size, bool writable=true, int fd=-1, off_t offset=0);
+            MemoryMapping(size_t size, mapping_mode mode, int fd=-1, off_t offset=0);
+
+            /// DEPRECATED: For backwards compatibility
+            MemoryMapping(size_t size, bool writable=true, int fd=-1, off_t offset=0) :
+                MemoryMapping(size, writable ? mapping_mode::write_shared : mapping_mode::readonly, fd, offset)  {
+            }
 
             /// You can not copy construct a MemoryMapping.
             MemoryMapping(const MemoryMapping&) = delete;
@@ -249,7 +263,7 @@ namespace osmium {
              * Was this mapping created as a writable mapping?
              */
             bool writable() const noexcept {
-                return m_writable;
+                return m_mapping_mode != mapping_mode::readonly;
             }
 
             /**
@@ -282,7 +296,7 @@ namespace osmium {
         public:
 
             AnonymousMemoryMapping(size_t size) :
-                MemoryMapping(size) {
+                MemoryMapping(size, mapping_mode::write_private) {
             }
 
 #ifndef __linux__
@@ -312,13 +326,13 @@ namespace osmium {
         public:
 
             /**
-             * Create anonymous memory mapping of given size.
+             * Create anonymous typed memory mapping of given size.
              *
              * @param size Number of objects of type T to be mapped
              * @throws std::system_error if the mapping fails
              */
             TypedMemoryMapping(size_t size) :
-                m_mapping(sizeof(T) * size) {
+                m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
             }
 
             /**
@@ -326,13 +340,18 @@ namespace osmium {
              * contain at least `sizeof(T) * size` bytes!
              *
              * @param size Number of objects of type T to be mapped
-             * @param writable Should the mapping be writable?
+             * @param mode Mapping mode: readonly, or writable (shared or private)
              * @param fd Open file descriptor of a file we want to map
              * @param offset Offset into the file where the mapping should start
              * @throws std::system_error if the mapping fails
              */
+            TypedMemoryMapping(size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
+                m_mapping(sizeof(T) * size, mode, fd, sizeof(T) * offset) {
+            }
+
+            /// DEPRECATED: For backwards compatibility
             TypedMemoryMapping(size_t size, bool writable, int fd, off_t offset = 0) :
-                m_mapping(sizeof(T) * size, writable, fd, sizeof(T) * offset) {
+                m_mapping(sizeof(T) * size, writable ? MemoryMapping::mapping_mode::write_shared : MemoryMapping::mapping_mode::readonly, fd, sizeof(T) * offset) {
             }
 
             /// You can not copy construct a TypedMemoryMapping.
@@ -499,26 +518,29 @@ inline void osmium::util::MemoryMapping::make_invalid() noexcept {
 #endif
 
 inline int osmium::util::MemoryMapping::get_protection() const noexcept {
-    if (m_writable) {
-        return PROT_READ | PROT_WRITE;
+    if (m_mapping_mode == mapping_mode::readonly) {
+        return PROT_READ;
     }
-    return PROT_READ;
+    return PROT_READ | PROT_WRITE;
 }
 
 inline int osmium::util::MemoryMapping::get_flags() const noexcept {
     if (m_fd == -1) {
         return MAP_PRIVATE | MAP_ANONYMOUS;
     }
-    return MAP_SHARED;
+    if (m_mapping_mode == mapping_mode::write_shared) {
+        return MAP_SHARED;
+    }
+    return MAP_PRIVATE;
 }
 
-inline osmium::util::MemoryMapping::MemoryMapping(size_t size, bool writable, int fd, off_t offset) :
+inline osmium::util::MemoryMapping::MemoryMapping(size_t size, mapping_mode mode, int fd, off_t offset) :
     m_size(initial_size(size)),
     m_offset(offset),
     m_fd(resize_fd(fd)),
-    m_writable(writable),
+    m_mapping_mode(mode),
     m_addr(::mmap(nullptr, m_size, get_protection(), get_flags(), m_fd, m_offset)) {
-    assert(writable || fd != -1);
+    assert(!(fd == -1 && mode == mapping_mode::readonly));
     if (!is_valid()) {
         throw std::system_error(errno, std::system_category(), "mmap failed");
     }
@@ -528,18 +550,18 @@ inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) :
     m_size(other.m_size),
     m_offset(other.m_offset),
     m_fd(other.m_fd),
-    m_writable(other.m_writable),
+    m_mapping_mode(other.m_mapping_mode),
     m_addr(other.m_addr) {
     other.make_invalid();
 }
 
 inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmium::util::MemoryMapping&& other) {
     unmap();
-    m_size     = other.m_size;
-    m_offset   = other.m_offset;
-    m_fd       = other.m_fd;
-    m_writable = other.m_writable;
-    m_addr     = other.m_addr;
+    m_size         = other.m_size;
+    m_offset       = other.m_offset;
+    m_fd           = other.m_fd;
+    m_mapping_mode = other.m_mapping_mode;
+    m_addr         = other.m_addr;
     other.make_invalid();
     return *this;
 }
@@ -604,20 +626,25 @@ namespace osmium {
 } // namespace osmium
 
 inline DWORD osmium::util::MemoryMapping::get_protection() const noexcept {
-    if (m_writable) {
-        return PAGE_READWRITE;
+    switch (m_mapping_mode) {
+        case mapping_mode::readonly:
+            return PAGE_READONLY;
+        case mapping_mode::write_private:
+            return PAGE_WRITECOPY;
+        case mapping_mode::write_shared:
+            return PAGE_READWRITE;
     }
-    return PAGE_READONLY;
 }
 
 inline DWORD osmium::util::MemoryMapping::get_flags() const noexcept {
-    if (m_fd == -1) {
-        return FILE_MAP_WRITE | FILE_MAP_COPY;
-    }
-    if (m_writable) {
-        return FILE_MAP_WRITE;
+    switch (m_mapping_mode) {
+        case mapping_mode::readonly:
+            return FILE_MAP_READ;
+        case mapping_mode::write_private:
+            return FILE_MAP_COPY;
+        case mapping_mode::write_shared:
+            return FILE_MAP_WRITE;
     }
-    return FILE_MAP_READ;
 }
 
 inline HANDLE osmium::util::MemoryMapping::get_handle() const noexcept {
@@ -643,11 +670,11 @@ inline void osmium::util::MemoryMapping::make_invalid() noexcept {
     m_addr = nullptr;
 }
 
-inline osmium::util::MemoryMapping::MemoryMapping(size_t size, bool writable, int fd, off_t offset) :
+inline osmium::util::MemoryMapping::MemoryMapping(size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
     m_size(initial_size(size)),
     m_offset(offset),
     m_fd(resize_fd(fd)),
-    m_writable(writable),
+    m_mapping_mode(mode),
     m_handle(create_file_mapping()),
     m_addr(nullptr) {
 
@@ -665,7 +692,7 @@ inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) :
     m_size(other.m_size),
     m_offset(other.m_offset),
     m_fd(other.m_fd),
-    m_writable(other.m_writable),
+    m_mapping_mode(other.m_mapping_mode),
     m_handle(std::move(other.m_handle)),
     m_addr(other.m_addr) {
     other.make_invalid();
@@ -674,12 +701,12 @@ inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) :
 
 inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmium::util::MemoryMapping&& other) {
     unmap();
-    m_size     = other.m_size;
-    m_offset   = other.m_offset;
-    m_fd       = other.m_fd;
-    m_writable = other.m_writable;
-    m_handle   = std::move(other.m_handle);
-    m_addr     = other.m_addr;
+    m_size         = other.m_size;
+    m_offset       = other.m_offset;
+    m_fd           = other.m_fd;
+    m_mapping_mode = other.m_mapping_mode;
+    m_handle       = std::move(other.m_handle);
+    m_addr         = other.m_addr;
     other.make_invalid();
     other.m_handle = nullptr;
     return *this;
diff --git a/include/osmium/util/minmax.hpp b/include/osmium/util/minmax.hpp
index 05bd39b..2eb601a 100644
--- a/include/osmium/util/minmax.hpp
+++ b/include/osmium/util/minmax.hpp
@@ -33,7 +33,6 @@ DEALINGS IN THE SOFTWARE.
 
 */
 
-#include <functional>
 #include <limits>
 
 namespace osmium {
@@ -116,8 +115,6 @@ namespace osmium {
 
     };
 
-
-
 } // namespace osmium
 
 #endif // OSMIUM_UTIL_MINMAX_HPP
diff --git a/include/protozero/byteswap.hpp b/include/protozero/byteswap.hpp
new file mode 100644
index 0000000..d019c28
--- /dev/null
+++ b/include/protozero/byteswap.hpp
@@ -0,0 +1,49 @@
+#ifndef PROTOZERO_BYTESWAP_HPP
+#define PROTOZERO_BYTESWAP_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+#include <cassert>
+
+namespace protozero {
+
+template <int N>
+inline void byteswap(const char* /*data*/, char* /*result*/) {
+    assert(false);
+}
+
+template <>
+inline void byteswap<1>(const char* data, char* result) {
+    result[0] = data[0];
+}
+
+template <>
+inline void byteswap<4>(const char* data, char* result) {
+    result[3] = data[0];
+    result[2] = data[1];
+    result[1] = data[2];
+    result[0] = data[3];
+}
+
+template <>
+inline void byteswap<8>(const char* data, char* result) {
+    result[7] = data[0];
+    result[6] = data[1];
+    result[5] = data[2];
+    result[4] = data[3];
+    result[3] = data[4];
+    result[2] = data[5];
+    result[1] = data[6];
+    result[0] = data[7];
+}
+
+} // end namespace protozero
+
+#endif // PROTOZERO_BYTESWAP_HPP
diff --git a/include/protozero/exception.hpp b/include/protozero/exception.hpp
new file mode 100644
index 0000000..1229f7d
--- /dev/null
+++ b/include/protozero/exception.hpp
@@ -0,0 +1,68 @@
+#ifndef PROTOZERO_EXCEPTION_HPP
+#define PROTOZERO_EXCEPTION_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file exception.hpp
+ *
+ * @brief Contains the exceptions used in the protozero library.
+ */
+
+#include <exception>
+
+/**
+ * @brief All parts of the protozero header-only library are in this namespace.
+ */
+namespace protozero {
+
+/**
+ * All exceptions explicitly thrown by the functions of the protozero library
+ * derive from this exception.
+ */
+struct exception : std::exception {
+    /// Returns the explanatory string.
+    const char *what() const noexcept { return "pbf exception"; }
+};
+
+/**
+ * This exception is thrown when parsing a varint thats larger than allowed.
+ * This should never happen unless the data is corrupted.
+ */
+struct varint_too_long_exception : exception {
+    /// Returns the explanatory string.
+    const char *what() const noexcept { return "varint too long exception"; }
+};
+
+/**
+ * This exception is thrown when the wire type of a pdf field is unknown.
+ * This should never happen unless the data is corrupted.
+ */
+struct unknown_pbf_wire_type_exception : exception {
+    /// Returns the explanatory string.
+    const char *what() const noexcept { return "unknown pbf field type exception"; }
+};
+
+/**
+ * This exception is thrown when we are trying to read a field and there
+ * are not enough bytes left in the buffer to read it. Almost all functions
+ * of the pbf_reader class can throw this exception.
+ *
+ * This should never happen unless the data is corrupted or you have
+ * initialized the pbf_reader object with incomplete data.
+ */
+struct end_of_buffer_exception : exception {
+    /// Returns the explanatory string.
+    const char *what() const noexcept { return "end of buffer exception"; }
+};
+
+} // end namespace protozero
+
+#endif // PROTOZERO_EXCEPTION_HPP
diff --git a/include/protozero/pbf_builder.hpp b/include/protozero/pbf_builder.hpp
new file mode 100644
index 0000000..d49a7ba
--- /dev/null
+++ b/include/protozero/pbf_builder.hpp
@@ -0,0 +1,111 @@
+#ifndef PROTOZERO_PBF_BUILDER_HPP
+#define PROTOZERO_PBF_BUILDER_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+#include <type_traits>
+
+#include <protozero/pbf_types.hpp>
+#include <protozero/pbf_writer.hpp>
+
+namespace protozero {
+
+template <typename T>
+class pbf_builder : public pbf_writer {
+
+    static_assert(std::is_same<pbf_tag_type, typename std::underlying_type<T>::type>::value, "T must be enum with underlying type protozero::pbf_tag_type");
+
+public:
+
+    using enum_type = T;
+
+    pbf_builder(std::string& data) noexcept :
+        pbf_writer(data) {
+    }
+
+    template <typename P>
+    pbf_builder(pbf_writer& parent_writer, P tag) noexcept :
+        pbf_writer(parent_writer, pbf_tag_type(tag)) {
+    }
+
+#define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \
+    inline void add_##name(T tag, type value) { \
+        pbf_writer::add_##name(pbf_tag_type(tag), value); \
+    }
+
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(bool, bool)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(enum, int32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(int32, int32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint32, int32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint32, uint32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(int64, int64_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint64, int64_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint64, uint64_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed32, uint32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed32, int32_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed64, uint64_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed64, int64_t)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(float, float)
+    PROTOZERO_WRITER_WRAP_ADD_SCALAR(double, double)
+
+    inline void add_bytes(T tag, const char* value, size_t size) {
+        pbf_writer::add_bytes(pbf_tag_type(tag), value, size);
+    }
+
+    inline void add_bytes(T tag, const std::string& value) {
+        pbf_writer::add_bytes(pbf_tag_type(tag), value);
+    }
+
+    inline void add_string(T tag, const char* value, size_t size) {
+        pbf_writer::add_string(pbf_tag_type(tag), value, size);
+    }
+
+    inline void add_string(T tag, const std::string& value) {
+        pbf_writer::add_string(pbf_tag_type(tag), value);
+    }
+
+    inline void add_string(T tag, const char* value) {
+        pbf_writer::add_string(pbf_tag_type(tag), value);
+    }
+
+    inline void add_message(T tag, const char* value, size_t size) {
+        pbf_writer::add_message(pbf_tag_type(tag), value, size);
+    }
+
+    inline void add_message(T tag, const std::string& value) {
+        pbf_writer::add_message(pbf_tag_type(tag), value);
+    }
+
+#define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \
+    template <typename InputIterator> \
+    inline void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
+        pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \
+    }
+
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(bool)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(enum)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(int32)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(sint32)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(uint32)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(int64)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(sint64)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(uint64)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed32)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed32)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed64)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed64)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(float)
+    PROTOZERO_WRITER_WRAP_ADD_PACKED(double)
+
+};
+
+} // end namespace protozero
+
+#endif // PROTOZERO_PBF_BUILDER_HPP
diff --git a/include/protozero/pbf_message.hpp b/include/protozero/pbf_message.hpp
new file mode 100644
index 0000000..af29a00
--- /dev/null
+++ b/include/protozero/pbf_message.hpp
@@ -0,0 +1,50 @@
+#ifndef PROTOZERO_PBF_MESSAGE_HPP
+#define PROTOZERO_PBF_MESSAGE_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+#include <type_traits>
+
+#include <protozero/pbf_reader.hpp>
+#include <protozero/pbf_types.hpp>
+
+namespace protozero {
+
+template <typename T>
+class pbf_message : public pbf_reader {
+
+    static_assert(std::is_same<pbf_tag_type, typename std::underlying_type<T>::type>::value, "T must be enum with underlying type protozero::pbf_tag_type");
+
+public:
+
+    using enum_type = T;
+
+    template <typename... Args>
+    pbf_message(Args&&... args) noexcept :
+        pbf_reader(std::forward<Args>(args)...) {
+    }
+
+    inline bool next() {
+        return pbf_reader::next();
+    }
+
+    inline bool next(T tag) {
+        return pbf_reader::next(pbf_tag_type(tag));
+    }
+
+    inline T tag() const noexcept {
+        return T(pbf_reader::tag());
+    }
+
+};
+
+} // end namespace protozero
+
+#endif // PROTOZERO_PBF_MESSAGE_HPP
diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp
new file mode 100644
index 0000000..1c5ed0d
--- /dev/null
+++ b/include/protozero/pbf_reader.hpp
@@ -0,0 +1,1059 @@
+#ifndef PROTOZERO_PBF_READER_HPP
+#define PROTOZERO_PBF_READER_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file pbf_reader.hpp
+ *
+ * @brief Contains the pbf_reader class.
+ */
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <string>
+#include <utility>
+
+#include <protozero/pbf_types.hpp>
+#include <protozero/exception.hpp>
+#include <protozero/varint.hpp>
+
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+# include <protozero/byteswap.hpp>
+#endif
+
+/// Wrapper for assert() used for testing
+#ifndef protozero_assert
+# define protozero_assert(x) assert(x)
+#endif
+
+namespace protozero {
+
+/**
+ * This class represents a protobuf message. Either a top-level message or
+ * a nested sub-message. Top-level messages can be created from any buffer
+ * with a pointer and length:
+ *
+ * @code
+ *    std::string buffer;
+ *    // fill buffer...
+ *    pbf_reader message(buffer.data(), buffer.size());
+ * @endcode
+ *
+ * Sub-messages are created using get_message():
+ *
+ * @code
+ *    pbf_reader message(...);
+ *    message.next();
+ *    pbf_reader submessage = message.get_message();
+ * @endcode
+ *
+ * All methods of the pbf_reader class except get_bytes() and get_string()
+ * provide the strong exception guarantee, ie they either succeed or do not
+ * change the pbf_reader object they are called on. Use the get_data() method
+ * instead of get_bytes() or get_string(), if you need this guarantee.
+ */
+class pbf_reader {
+
+    // A pointer to the next unread data.
+    const char *m_data = nullptr;
+
+    // A pointer to one past the end of data.
+    const char *m_end = nullptr;
+
+    // The wire type of the current field.
+    pbf_wire_type m_wire_type = pbf_wire_type::unknown;
+
+    // The tag of the current field.
+    pbf_tag_type m_tag = 0;
+
+    template <typename T>
+    inline T get_fixed() {
+        T result;
+        skip_bytes(sizeof(T));
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+        memcpy(&result, m_data - sizeof(T), sizeof(T));
+#else
+        byteswap<sizeof(T)>(m_data - sizeof(T), reinterpret_cast<char*>(&result));
+#endif
+        return result;
+    }
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    template <typename T>
+    inline std::pair<const T*, const T*> packed_fixed() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        auto len = get_len_and_skip();
+        protozero_assert(len % sizeof(T) == 0);
+        return std::make_pair(reinterpret_cast<const T*>(m_data-len), reinterpret_cast<const T*>(m_data));
+    }
+
+#else
+
+    template <typename T>
+    class const_fixed_iterator : public std::iterator<std::forward_iterator_tag, T> {
+
+        const char* m_data;
+        const char* m_end;
+
+    public:
+
+        const_fixed_iterator() noexcept :
+            m_data(nullptr),
+            m_end(nullptr) {
+        }
+
+        const_fixed_iterator(const char *data, const char* end) noexcept :
+            m_data(data),
+            m_end(end) {
+        }
+
+        const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
+        const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
+
+        const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
+        const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
+
+        ~const_fixed_iterator() noexcept = default;
+
+        T operator*() {
+            T result;
+            byteswap<sizeof(T)>(m_data, reinterpret_cast<char*>(&result));
+            return result;
+        }
+
+        const_fixed_iterator& operator++() {
+            m_data += sizeof(T);
+            return *this;
+        }
+
+        const_fixed_iterator operator++(int) {
+            const const_fixed_iterator tmp(*this);
+            ++(*this);
+            return tmp;
+        }
+
+        bool operator==(const const_fixed_iterator& rhs) const noexcept {
+            return m_data == rhs.m_data && m_end == rhs.m_end;
+        }
+
+        bool operator!=(const const_fixed_iterator& rhs) const noexcept {
+            return !(*this == rhs);
+        }
+
+    }; // class const_fixed_iterator
+
+    template <typename T>
+    inline std::pair<const_fixed_iterator<T>, const_fixed_iterator<T>> packed_fixed() {
+        protozero_assert(tag() != 0 && "call next() before accessing field value");
+        auto len = get_len_and_skip();
+        protozero_assert(len % sizeof(T) == 0);
+        return std::make_pair(const_fixed_iterator<T>(m_data-len, m_data),
+                              const_fixed_iterator<T>(m_data, m_data));
+    }
+#endif
+
+    template <typename T> inline T get_varint();
+    template <typename T> inline T get_svarint();
+
+    inline pbf_length_type get_length() { return get_varint<pbf_length_type>(); }
+
+    inline void skip_bytes(pbf_length_type len);
+
+    inline pbf_length_type get_len_and_skip();
+
+public:
+
+    /**
+     * 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. So you must
+     * make sure the buffer stays valid as long as the pbf_reader object is used.
+     *
+     * The buffer must contain a complete protobuf message.
+     *
+     * @post There is no current field.
+     */
+    inline pbf_reader(const char *data, size_t length) noexcept;
+
+    /**
+     * 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. So you must
+     * make sure the buffer stays valid as long as the pbf_reader object is used.
+     *
+     * The buffer must contain a complete protobuf message.
+     *
+     * @post There is no current field.
+     */
+    inline pbf_reader(std::pair<const char *, size_t> data) noexcept;
+
+    /**
+     * Construct a pbf_reader message from a std::string. A pointer to the string
+     * internals will be stored inside the pbf_reader object, no data is copied.
+     * So you must make sure the string is unchanged as long as the pbf_reader
+     * object is used.
+     *
+     * The string must contain a complete protobuf message.
+     *
+     * @post There is no current field.
+     */
+    inline pbf_reader(const std::string& data) noexcept;
+
+    /**
+     * pbf_reader can be default constructed and behaves like it has an empty
+     * buffer.
+     */
+    inline pbf_reader() noexcept = default;
+
+    /// pbf_reader messages can be copied trivially.
+    inline pbf_reader(const pbf_reader&) noexcept = default;
+
+    /// pbf_reader messages can be moved trivially.
+    inline pbf_reader(pbf_reader&&) noexcept = default;
+
+    /// pbf_reader messages can be copied trivially.
+    inline pbf_reader& operator=(const pbf_reader& other) noexcept = default;
+
+    /// pbf_reader messages can be moved trivially.
+    inline pbf_reader& operator=(pbf_reader&& other) noexcept = default;
+
+    inline ~pbf_reader() = default;
+
+    /**
+     * In a boolean context the pbf_reader class evaluates to `true` if there are
+     * still fields available and to `false` if the last field has been read.
+     */
+    inline operator bool() const noexcept;
+
+    /**
+     * Return the length in bytes of the current message. If you have
+     * already called next() and/or any of the get_*() functions, this will
+     * return the remaining length.
+     *
+     * This can, for instance, be used to estimate the space needed for a
+     * buffer. Of course you have to know reasonably well what data to expect
+     * and how it is encoded for this number to have any meaning.
+     */
+    size_t length() const noexcept {
+        return size_t(m_end - m_data);
+    }
+
+    /**
+     * Set next field in the message as the current field. This is usually
+     * called in a while loop:
+     *
+     * @code
+     *    pbf_reader message(...);
+     *    while (message.next()) {
+     *        // handle field
+     *    }
+     * @endcode
+     *
+     * @returns `true` if there is a next field, `false` if not.
+     * @pre There must be no current field.
+     * @post If it returns `true` there is a current field now.
+     */
+    inline bool next();
+
+    /**
+     * Set next field with given tag in the message as the current field.
+     * Fields with other tags are skipped. This is usually called in a while
+     * loop for repeated fields:
+     *
+     * @code
+     *    pbf_reader message(...);
+     *    while (message.next(17)) {
+     *        // handle field
+     *    }
+     * @endcode
+     *
+     * or you can call it just once to get the one field with this tag:
+     *
+     * @code
+     *    pbf_reader message(...);
+     *    if (message.next(17)) {
+     *        // handle field
+     *    }
+     * @endcode
+     *
+     * @returns `true` if there is a next field with this tag.
+     * @pre There must be no current field.
+     * @post If it returns `true` there is a current field now with the given tag.
+     */
+    inline bool next(pbf_tag_type tag);
+
+    /**
+     * The tag of the current field. The tag is the field number from the
+     * description in the .proto file.
+     *
+     * Call next() before calling this function to set the current field.
+     *
+     * @returns tag of the current field.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     */
+    inline pbf_tag_type tag() const noexcept;
+
+    /**
+     * Get the wire type of the current field. The wire types are:
+     *
+     * * 0 - varint
+     * * 1 - 64 bit
+     * * 2 - length-delimited
+     * * 5 - 32 bit
+     *
+     * All other types are illegal.
+     *
+     * Call next() before calling this function to set the current field.
+     *
+     * @returns wire type of the current field.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     */
+    inline pbf_wire_type wire_type() const noexcept;
+
+    /**
+     * Check the wire type of the current field.
+     *
+     * @returns `true` if the current field has the given wire type.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     */
+    inline bool has_wire_type(pbf_wire_type type) const noexcept;
+
+    /**
+     * Consume the current field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline void skip();
+
+    ///@{
+    /**
+     * @name Scalar field accessor functions
+     */
+
+    /**
+     * Consume and return value of current "bool" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "bool".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline bool get_bool();
+
+    /**
+     * Consume and return value of current "enum" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "enum".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int32_t get_enum() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_varint<int32_t>();
+    }
+
+    /**
+     * Consume and return value of current "int32" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "int32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int32_t get_int32() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_varint<int32_t>();
+    }
+
+    /**
+     * Consume and return value of current "sint32" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "sint32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int32_t get_sint32() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_svarint<int32_t>();
+    }
+
+    /**
+     * Consume and return value of current "uint32" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "uint32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline uint32_t get_uint32() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_varint<uint32_t>();
+    }
+
+    /**
+     * Consume and return value of current "int64" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "int64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int64_t get_int64() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_varint<int64_t>();
+    }
+
+    /**
+     * Consume and return value of current "sint64" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "sint64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int64_t get_sint64() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_svarint<int64_t>();
+    }
+
+    /**
+     * Consume and return value of current "uint64" varint field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "uint64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline uint64_t get_uint64() {
+        protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
+        return get_varint<uint64_t>();
+    }
+
+    /**
+     * Consume and return value of current "fixed32" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "fixed32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline uint32_t get_fixed32();
+
+    /**
+     * Consume and return value of current "sfixed32" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "sfixed32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int32_t get_sfixed32();
+
+    /**
+     * Consume and return value of current "fixed64" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "fixed64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline uint64_t get_fixed64();
+
+    /**
+     * Consume and return value of current "sfixed64" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "sfixed64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline int64_t get_sfixed64();
+
+    /**
+     * Consume and return value of current "float" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "float".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline float get_float();
+
+    /**
+     * Consume and return value of current "double" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "double".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline double get_double();
+
+    /**
+     * Consume and return value of current "bytes" or "string" field.
+     *
+     * @returns A pair with a pointer to the data and the length of the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "bytes" or "string".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<const char*, pbf_length_type> get_data();
+
+    /**
+     * Consume and return value of current "bytes" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "bytes".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::string get_bytes();
+
+    /**
+     * Consume and return value of current "string" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "string".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::string get_string();
+
+    /**
+     * Consume and return value of current "message" field.
+     *
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "message".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline pbf_reader get_message() {
+        return pbf_reader(get_data());
+    }
+
+    ///@}
+
+private:
+
+    template <typename T>
+    class const_varint_iterator : public std::iterator<std::forward_iterator_tag, T> {
+
+    protected:
+
+        const char* m_data;
+        const char* m_end;
+
+    public:
+
+        const_varint_iterator() noexcept :
+            m_data(nullptr),
+            m_end(nullptr) {
+        }
+
+        const_varint_iterator(const char *data, const char* end) noexcept :
+            m_data(data),
+            m_end(end) {
+        }
+
+        const_varint_iterator(const const_varint_iterator&) noexcept = default;
+        const_varint_iterator(const_varint_iterator&&) noexcept = default;
+
+        const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
+        const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
+
+        ~const_varint_iterator() noexcept = default;
+
+        T operator*() {
+            const char* d = m_data; // will be thrown away
+            return static_cast<T>(decode_varint(&d, m_end));
+        }
+
+        const_varint_iterator& operator++() {
+            // Ignore the result, we call decode_varint() just for the
+            // side-effect of updating m_data.
+            decode_varint(&m_data, m_end);
+            return *this;
+        }
+
+        const_varint_iterator operator++(int) {
+            const const_varint_iterator tmp(*this);
+            ++(*this);
+            return tmp;
+        }
+
+        bool operator==(const const_varint_iterator& rhs) const noexcept {
+            return m_data == rhs.m_data && m_end == rhs.m_end;
+        }
+
+        bool operator!=(const const_varint_iterator& rhs) const noexcept {
+            return !(*this == rhs);
+        }
+
+    }; // class const_varint_iterator
+
+    template <typename T>
+    class const_svarint_iterator : public const_varint_iterator<T> {
+
+    public:
+
+        const_svarint_iterator() noexcept :
+            const_varint_iterator<T>() {
+        }
+
+        const_svarint_iterator(const char *data, const char* end) noexcept :
+            const_varint_iterator<T>(data, end) {
+        }
+
+        const_svarint_iterator(const const_svarint_iterator&) = default;
+        const_svarint_iterator(const_svarint_iterator&&) = default;
+
+        const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
+        const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
+
+        ~const_svarint_iterator() = default;
+
+        T operator*() {
+            const char* d = this->m_data; // will be thrown away
+            return static_cast<T>(decode_zigzag64(decode_varint(&d, this->m_end)));
+        }
+
+        const_svarint_iterator& operator++() {
+            // Ignore the result, we call decode_varint() just for the
+            // side-effect of updating m_data.
+            decode_varint(&this->m_data, this->m_end);
+            return *this;
+        }
+
+        const_svarint_iterator operator++(int) {
+            const const_svarint_iterator tmp(*this);
+            ++(*this);
+            return tmp;
+        }
+
+    }; // class const_svarint_iterator
+
+public:
+
+    /// Forward iterator for iterating over bool (int32 varint) values.
+    typedef const_varint_iterator< int32_t> const_bool_iterator;
+
+    /// Forward iterator for iterating over enum (int32 varint) values.
+    typedef const_varint_iterator< int32_t> const_enum_iterator;
+
+    /// Forward iterator for iterating over int32 (varint) values.
+    typedef const_varint_iterator< int32_t> const_int32_iterator;
+
+    /// Forward iterator for iterating over sint32 (varint) values.
+    typedef const_svarint_iterator<int32_t> const_sint32_iterator;
+
+    /// Forward iterator for iterating over uint32 (varint) values.
+    typedef const_varint_iterator<uint32_t> const_uint32_iterator;
+
+    /// Forward iterator for iterating over int64 (varint) values.
+    typedef const_varint_iterator< int64_t> const_int64_iterator;
+
+    /// Forward iterator for iterating over sint64 (varint) values.
+    typedef const_svarint_iterator<int64_t> const_sint64_iterator;
+
+    /// Forward iterator for iterating over uint64 (varint) values.
+    typedef const_varint_iterator<uint64_t> const_uint64_iterator;
+
+    ///@{
+    /**
+     * @name Repeated packed field accessor functions
+     */
+
+    /**
+     * Consume current "repeated packed bool" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed bool".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> get_packed_bool();
+
+    /**
+     * Consume current "repeated packed enum" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed enum".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> get_packed_enum();
+
+    /**
+     * Consume current "repeated packed int32" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed int32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> get_packed_int32();
+
+    /**
+     * Consume current "repeated packed sint32" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed sint32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> get_packed_sint32();
+
+    /**
+     * Consume current "repeated packed uint32" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed uint32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> get_packed_uint32();
+
+    /**
+     * Consume current "repeated packed int64" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed int64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> get_packed_int64();
+
+    /**
+     * Consume current "repeated packed sint64" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed sint64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> get_packed_sint64();
+
+    /**
+     * Consume current "repeated packed uint64" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed uint64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> get_packed_uint64();
+
+    /**
+     * Consume current "repeated packed fixed32" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed fixed32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_fixed32() -> decltype(packed_fixed<uint32_t>()) {
+        return packed_fixed<uint32_t>();
+    }
+
+    /**
+     * Consume current "repeated packed sfixed32" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed sfixed32".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_sfixed32() -> decltype(packed_fixed<int32_t>()) {
+        return packed_fixed<int32_t>();
+    }
+
+    /**
+     * Consume current "repeated packed fixed64" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed fixed64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_fixed64() -> decltype(packed_fixed<uint64_t>()) {
+        return packed_fixed<uint64_t>();
+    }
+
+    /**
+     * Consume current "repeated packed sfixed64" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed sfixed64".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_sfixed64() -> decltype(packed_fixed<int64_t>()) {
+        return packed_fixed<int64_t>();
+    }
+
+    /**
+     * Consume current "repeated packed float" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed float".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_float() -> decltype(packed_fixed<float>()) {
+        return packed_fixed<float>();
+    }
+
+    /**
+     * Consume current "repeated packed double" field.
+     *
+     * @returns a pair of iterators to the beginning and one past the end of
+     *          the data.
+     * @pre There must be a current field (ie. next() must have returned `true`).
+     * @pre The current field must be of type "repeated packed double".
+     * @post The current field was consumed and there is no current field now.
+     */
+    inline auto get_packed_double() -> decltype(packed_fixed<double>()) {
+        return packed_fixed<double>();
+    }
+
+    ///@}
+
+}; // class pbf_reader
+
+pbf_reader::pbf_reader(const char *data, size_t length) noexcept
+    : m_data(data),
+      m_end(data + length),
+      m_wire_type(pbf_wire_type::unknown),
+      m_tag(0) {
+}
+
+pbf_reader::pbf_reader(std::pair<const char *, size_t> data) noexcept
+    : m_data(data.first),
+      m_end(data.first + data.second),
+      m_wire_type(pbf_wire_type::unknown),
+      m_tag(0) {
+}
+
+pbf_reader::pbf_reader(const std::string& data) noexcept
+    : m_data(data.data()),
+      m_end(data.data() + data.size()),
+      m_wire_type(pbf_wire_type::unknown),
+      m_tag(0) {
+}
+
+pbf_reader::operator bool() const noexcept {
+    return m_data < m_end;
+}
+
+bool pbf_reader::next() {
+    if (m_data == m_end) {
+        return false;
+    }
+
+    auto value = get_varint<uint32_t>();
+    m_tag = 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");
+
+    m_wire_type = pbf_wire_type(value & 0x07);
+// XXX do we want this check? or should it throw an exception?
+//        protozero_assert((m_wire_type <=2 || m_wire_type == 5) && "illegal wire type");
+    return true;
+}
+
+bool pbf_reader::next(pbf_tag_type requested_tag) {
+    while (next()) {
+        if (m_tag == requested_tag) {
+            return true;
+        } else {
+            skip();
+        }
+    }
+    return false;
+}
+
+pbf_tag_type pbf_reader::tag() const noexcept {
+    return m_tag;
+}
+
+pbf_wire_type pbf_reader::wire_type() const noexcept {
+    return m_wire_type;
+}
+
+bool pbf_reader::has_wire_type(pbf_wire_type type) const noexcept {
+    return wire_type() == type;
+}
+
+void pbf_reader::skip_bytes(pbf_length_type len) {
+    if (m_data + len > m_end) {
+        throw end_of_buffer_exception();
+    }
+    m_data += len;
+
+// In debug builds reset the tag to zero so that we can detect (some)
+// wrong code.
+#ifndef NDEBUG
+    m_tag = 0;
+#endif
+}
+
+void pbf_reader::skip() {
+    protozero_assert(tag() != 0 && "call next() before calling skip()");
+    switch (wire_type()) {
+        case pbf_wire_type::varint:
+            (void)get_uint32(); // called for the side-effect of skipping value
+            break;
+        case pbf_wire_type::fixed64:
+            skip_bytes(8);
+            break;
+        case pbf_wire_type::length_delimited:
+            skip_bytes(get_length());
+            break;
+        case pbf_wire_type::fixed32:
+            skip_bytes(4);
+            break;
+        default:
+            throw unknown_pbf_wire_type_exception();
+    }
+}
+
+pbf_length_type pbf_reader::get_len_and_skip() {
+    auto len = get_length();
+    skip_bytes(len);
+    return len;
+}
+
+template <typename T>
+T pbf_reader::get_varint() {
+    return static_cast<T>(decode_varint(&m_data, m_end));
+}
+
+template <typename T>
+T pbf_reader::get_svarint() {
+    protozero_assert((has_wire_type(pbf_wire_type::varint) || has_wire_type(pbf_wire_type::length_delimited)) && "not a varint");
+    return static_cast<T>(decode_zigzag64(decode_varint(&m_data, m_end)));
+}
+
+uint32_t pbf_reader::get_fixed32() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+    return get_fixed<uint32_t>();
+}
+
+int32_t pbf_reader::get_sfixed32() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+    return get_fixed<int32_t>();
+}
+
+uint64_t pbf_reader::get_fixed64() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+    return get_fixed<uint64_t>();
+}
+
+int64_t pbf_reader::get_sfixed64() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+    return get_fixed<int64_t>();
+}
+
+float pbf_reader::get_float() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed32) && "not a 32-bit fixed");
+    return get_fixed<float>();
+}
+
+double pbf_reader::get_double() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::fixed64) && "not a 64-bit fixed");
+    return get_fixed<double>();
+}
+
+bool pbf_reader::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
+}
+
+std::pair<const char*, pbf_length_type> pbf_reader::get_data() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message");
+    auto len = get_len_and_skip();
+    return std::make_pair(m_data-len, len);
+}
+
+std::string pbf_reader::get_bytes() {
+    auto d = get_data();
+    return std::string(d.first, d.second);
+}
+
+std::string pbf_reader::get_string() {
+    return get_bytes();
+}
+
+std::pair<pbf_reader::const_bool_iterator, pbf_reader::const_bool_iterator> pbf_reader::get_packed_bool() {
+    return get_packed_int32();
+}
+
+std::pair<pbf_reader::const_enum_iterator, pbf_reader::const_enum_iterator> pbf_reader::get_packed_enum() {
+    return get_packed_int32();
+}
+
+std::pair<pbf_reader::const_int32_iterator, pbf_reader::const_int32_iterator> pbf_reader::get_packed_int32() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_int32_iterator(m_data-len, m_data),
+                          pbf_reader::const_int32_iterator(m_data, m_data));
+}
+
+std::pair<pbf_reader::const_uint32_iterator, pbf_reader::const_uint32_iterator> pbf_reader::get_packed_uint32() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_uint32_iterator(m_data-len, m_data),
+                          pbf_reader::const_uint32_iterator(m_data, m_data));
+}
+
+std::pair<pbf_reader::const_sint32_iterator, pbf_reader::const_sint32_iterator> pbf_reader::get_packed_sint32() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_sint32_iterator(m_data-len, m_data),
+                          pbf_reader::const_sint32_iterator(m_data, m_data));
+}
+
+std::pair<pbf_reader::const_int64_iterator, pbf_reader::const_int64_iterator> pbf_reader::get_packed_int64() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_int64_iterator(m_data-len, m_data),
+                          pbf_reader::const_int64_iterator(m_data, m_data));
+}
+
+std::pair<pbf_reader::const_uint64_iterator, pbf_reader::const_uint64_iterator> pbf_reader::get_packed_uint64() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_uint64_iterator(m_data-len, m_data),
+                          pbf_reader::const_uint64_iterator(m_data, m_data));
+}
+
+std::pair<pbf_reader::const_sint64_iterator, pbf_reader::const_sint64_iterator> pbf_reader::get_packed_sint64() {
+    protozero_assert(tag() != 0 && "call next() before accessing field value");
+    auto len = get_len_and_skip();
+    return std::make_pair(pbf_reader::const_sint64_iterator(m_data-len, m_data),
+                          pbf_reader::const_sint64_iterator(m_data, m_data));
+}
+
+} // end namespace protozero
+
+#endif // PROTOZERO_PBF_READER_HPP
diff --git a/include/protozero/pbf_types.hpp b/include/protozero/pbf_types.hpp
new file mode 100644
index 0000000..9f38584
--- /dev/null
+++ b/include/protozero/pbf_types.hpp
@@ -0,0 +1,49 @@
+#ifndef PROTOZERO_PBF_TYPES_HPP
+#define PROTOZERO_PBF_TYPES_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file pbf_types.hpp
+ *
+ * @brief Contains the declaration of low-level types used in the pbf format.
+ */
+
+#include <cstdint>
+
+namespace protozero {
+
+    /**
+     * The type used for field tags (field numbers).
+     */
+    typedef uint32_t pbf_tag_type;
+
+    /**
+     * 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
+    };
+
+    /**
+     * The type used for length values, such as the length of a field.
+     */
+    typedef uint32_t pbf_length_type;
+
+} // end namespace protozero
+
+#endif // PROTOZERO_PBF_TYPES_HPP
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
new file mode 100644
index 0000000..53cbfdf
--- /dev/null
+++ b/include/protozero/pbf_writer.hpp
@@ -0,0 +1,664 @@
+#ifndef PROTOZERO_PBF_WRITER_HPP
+#define PROTOZERO_PBF_WRITER_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file pbf_writer.hpp
+ *
+ * @brief Contains the pbf_writer class.
+ */
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <string>
+
+#include <protozero/pbf_types.hpp>
+#include <protozero/varint.hpp>
+
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+# include <protozero/byteswap.hpp>
+#endif
+
+/// Wrapper for assert() used for testing
+#ifndef protozero_assert
+# define protozero_assert(x) assert(x)
+#endif
+
+namespace protozero {
+
+/**
+ * The pbf_writer is used to write PBF formatted messages into a buffer.
+ *
+ * Almost all methods in this class can throw an std::bad_alloc exception if
+ * the std::string used as a buffer wants to resize.
+ */
+class pbf_writer {
+
+    std::string* m_data;
+    pbf_writer* m_parent_writer;
+    size_t m_pos = 0;
+
+    inline void add_varint(uint64_t value) {
+        protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
+        protozero_assert(m_data);
+        write_varint(std::back_inserter(*m_data), value);
+    }
+
+    inline void add_field(pbf_tag_type tag, pbf_wire_type type) {
+        protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range");
+        uint32_t b = (tag << 3) | uint32_t(type);
+        add_varint(b);
+    }
+
+    inline void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
+        add_field(tag, pbf_wire_type::varint);
+        add_varint(value);
+    }
+
+    template <typename T>
+    inline void add_fixed(T value) {
+        protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
+        protozero_assert(m_data);
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+        m_data->append(reinterpret_cast<const char*>(&value), sizeof(T));
+#else
+        auto size = m_data->size();
+        m_data->resize(size + sizeof(T));
+        byteswap<sizeof(T)>(reinterpret_cast<const char*>(&value), const_cast<char*>(m_data->data() + size));
+#endif
+    }
+
+    template <typename T, typename It>
+    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
+        if (first == last) {
+            return;
+        }
+
+        pbf_writer sw(*this, tag);
+
+        while (first != last) {
+            sw.add_fixed<T>(*first++);
+        }
+    }
+
+    template <typename T, typename It>
+    inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
+        if (first == last) {
+            return;
+        }
+
+        add_length_varint(tag, sizeof(T) * pbf_length_type(std::distance(first, last)));
+
+        while (first != last) {
+            add_fixed<T>(*first++);
+        }
+    }
+
+    template <typename It>
+    inline void add_packed_varint(pbf_tag_type tag, It first, It last) {
+        if (first == last) {
+            return;
+        }
+
+        pbf_writer sw(*this, tag);
+
+        while (first != last) {
+            sw.add_varint(uint64_t(*first++));
+        }
+    }
+
+    template <typename It>
+    inline void add_packed_svarint(pbf_tag_type tag, It first, It last) {
+        if (first == last) {
+            return;
+        }
+
+        pbf_writer sw(*this, tag);
+
+        while (first != last) {
+            sw.add_varint(encode_zigzag64(*first++));
+        }
+    }
+
+    // The number of bytes to reserve for the varint holding the length of
+    // a length-delimited field. The length has to fit into pbf_length_type,
+    // and a varint needs 8 bit for every 7 bit.
+    static const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
+
+    inline void open_submessage(pbf_tag_type tag) {
+        protozero_assert(m_pos == 0);
+        protozero_assert(m_data);
+        add_field(tag, pbf_wire_type::length_delimited);
+        m_data->append(size_t(reserve_bytes), '\0');
+        m_pos = m_data->size();
+    }
+
+    inline void close_submessage() {
+        protozero_assert(m_pos != 0);
+        protozero_assert(m_data);
+        auto length = pbf_length_type(m_data->size() - m_pos);
+
+        protozero_assert(m_data->size() >= m_pos - reserve_bytes);
+        auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
+
+        m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos));
+        m_pos = 0;
+    }
+
+    inline void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
+        add_field(tag, pbf_wire_type::length_delimited);
+        add_varint(length);
+    }
+
+public:
+
+    /**
+     * Create a writer using the given string as a data store. The pbf_writer
+     * stores a reference to that string and adds all data to it.
+     */
+    inline explicit pbf_writer(std::string& data) noexcept :
+        m_data(&data),
+        m_parent_writer(nullptr),
+        m_pos(0) {
+    }
+
+    /**
+     * Create a writer without a data store. In this form the writer can not
+     * be used!
+     */
+    inline pbf_writer() noexcept :
+        m_data(nullptr),
+        m_parent_writer(nullptr),
+        m_pos(0) {
+    }
+
+    /**
+     * Construct a pbf_writer for a submessage from the pbf_writer of the
+     * parent message.
+     *
+     * @param parent_writer The pbf_writer
+     * @param tag Tag (field number) of the field that will be written
+     */
+    inline pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag) :
+        m_data(parent_writer.m_data),
+        m_parent_writer(&parent_writer),
+        m_pos(0) {
+        m_parent_writer->open_submessage(tag);
+    }
+
+    /// A pbf_writer object can be copied
+    pbf_writer(const pbf_writer&) noexcept = default;
+
+    /// A pbf_writer object can be copied
+    pbf_writer& operator=(const pbf_writer&) noexcept = default;
+
+    /// A pbf_writer object can be moved
+    inline pbf_writer(pbf_writer&&) noexcept = default;
+
+    /// A pbf_writer object can be moved
+    inline pbf_writer& operator=(pbf_writer&&) noexcept = default;
+
+    inline ~pbf_writer() {
+        if (m_parent_writer) {
+            m_parent_writer->close_submessage();
+        }
+    }
+
+    ///@{
+    /**
+     * @name Scalar field writer functions
+     */
+
+    /**
+     * Add "bool" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_bool(pbf_tag_type tag, bool value) {
+        add_field(tag, pbf_wire_type::varint);
+        add_fixed<char>(value);
+    }
+
+    /**
+     * Add "enum" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_enum(pbf_tag_type tag, int32_t value) {
+        add_tagged_varint(tag, uint64_t(value));
+    }
+
+    /**
+     * Add "int32" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_int32(pbf_tag_type tag, int32_t value) {
+        add_tagged_varint(tag, uint64_t(value));
+    }
+
+    /**
+     * Add "sint32" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_sint32(pbf_tag_type tag, int32_t value) {
+        add_tagged_varint(tag, encode_zigzag32(value));
+    }
+
+    /**
+     * Add "uint32" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_uint32(pbf_tag_type tag, uint32_t value) {
+        add_tagged_varint(tag, value);
+    }
+
+    /**
+     * Add "int64" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_int64(pbf_tag_type tag, int64_t value) {
+        add_tagged_varint(tag, uint64_t(value));
+    }
+
+    /**
+     * Add "sint64" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_sint64(pbf_tag_type tag, int64_t value) {
+        add_tagged_varint(tag, encode_zigzag64(value));
+    }
+
+    /**
+     * Add "uint64" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_uint64(pbf_tag_type tag, uint64_t value) {
+        add_tagged_varint(tag, value);
+    }
+
+    /**
+     * Add "fixed32" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_fixed32(pbf_tag_type tag, uint32_t value) {
+        add_field(tag, pbf_wire_type::fixed32);
+        add_fixed<uint32_t>(value);
+    }
+
+    /**
+     * Add "sfixed32" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_sfixed32(pbf_tag_type tag, int32_t value) {
+        add_field(tag, pbf_wire_type::fixed32);
+        add_fixed<int32_t>(value);
+    }
+
+    /**
+     * Add "fixed64" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_fixed64(pbf_tag_type tag, uint64_t value) {
+        add_field(tag, pbf_wire_type::fixed64);
+        add_fixed<uint64_t>(value);
+    }
+
+    /**
+     * Add "sfixed64" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_sfixed64(pbf_tag_type tag, int64_t value) {
+        add_field(tag, pbf_wire_type::fixed64);
+        add_fixed<int64_t>(value);
+    }
+
+    /**
+     * Add "float" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_float(pbf_tag_type tag, float value) {
+        add_field(tag, pbf_wire_type::fixed32);
+        add_fixed<float>(value);
+    }
+
+    /**
+     * Add "double" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_double(pbf_tag_type tag, double value) {
+        add_field(tag, pbf_wire_type::fixed64);
+        add_fixed<double>(value);
+    }
+
+    /**
+     * Add "bytes" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Pointer to value to be written
+     * @param size Number of bytes to be written
+     */
+    inline void add_bytes(pbf_tag_type tag, const char* value, size_t size) {
+        protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
+        protozero_assert(m_data);
+        assert(size <= std::numeric_limits<pbf_length_type>::max());
+        add_length_varint(tag, pbf_length_type(size));
+        m_data->append(value, size);
+    }
+
+    /**
+     * Add "bytes" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_bytes(pbf_tag_type tag, const std::string& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "string" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Pointer to value to be written
+     * @param size Number of bytes to be written
+     */
+    inline void add_string(pbf_tag_type tag, const char* value, size_t size) {
+        add_bytes(tag, value, size);
+    }
+
+    /**
+     * Add "string" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written
+     */
+    inline void add_string(pbf_tag_type tag, const std::string& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    /**
+     * Add "string" field to data. Bytes from the value are written until
+     * a null byte is encountered. The null byte is not added.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Pointer to value to be written
+     */
+    inline void add_string(pbf_tag_type tag, const char* value) {
+        add_bytes(tag, value, std::strlen(value));
+    }
+
+    /**
+     * Add "message" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Pointer to message to be written
+     * @param size Length of the message
+     */
+    inline void add_message(pbf_tag_type tag, const char* value, size_t size) {
+        add_bytes(tag, value, size);
+    }
+
+    /**
+     * Add "message" field to data.
+     *
+     * @param tag Tag (field number) of the field
+     * @param value Value to be written. The value must be a complete message.
+     */
+    inline void add_message(pbf_tag_type tag, const std::string& value) {
+        add_bytes(tag, value.data(), value.size());
+    }
+
+    ///@}
+
+    ///@{
+    /**
+     * @name Repeated packed field writer functions
+     */
+
+    /**
+     * Add "repeated packed bool" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to bool.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed enum" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed int32" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed sint32" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_svarint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed uint32" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to uint32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed int64" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int64_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed sint64" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int64_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_svarint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed uint64" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to uint64_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_varint(tag, first, last);
+    }
+
+    /**
+     * Add "repeated packed fixed32" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to uint32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<uint32_t, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    /**
+     * Add "repeated packed sfixed32" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int32_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<int32_t, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    /**
+     * Add "repeated packed fixed64" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to uint64_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<uint64_t, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    /**
+     * Add "repeated packed sfixed64" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to int64_t.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<int64_t, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    /**
+     * Add "repeated packed float" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to float.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<float, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    /**
+     * Add "repeated packed double" field to data.
+     *
+     * @tparam InputIterator An type satisfying the InputIterator concept.
+     *         Dereferencing the iterator must yield a type assignable to double.
+     * @param tag Tag (field number) of the field
+     * @param first Iterator pointing to the beginning of the data
+     * @param last Iterator pointing one past the end of data
+     */
+    template <typename InputIterator>
+    inline void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
+        add_packed_fixed<double, InputIterator>(tag, first, last,
+            typename std::iterator_traits<InputIterator>::iterator_category());
+    }
+
+    ///@}
+
+}; // class pbf_writer
+
+} // end namespace protozero
+
+#endif // PROTOZERO_PBF_WRITER_HPP
diff --git a/include/protozero/varint.hpp b/include/protozero/varint.hpp
new file mode 100644
index 0000000..bc9c329
--- /dev/null
+++ b/include/protozero/varint.hpp
@@ -0,0 +1,136 @@
+#ifndef PROTOZERO_VARINT_HPP
+#define PROTOZERO_VARINT_HPP
+
+/*****************************************************************************
+
+protozero - Minimalistic protocol buffer decoder and encoder in C++.
+
+This file is from https://github.com/mapbox/protozero where you can find more
+documentation.
+
+*****************************************************************************/
+
+/**
+ * @file varint.hpp
+ *
+ * @brief Contains low-level varint and zigzag encoding and decoding functions.
+ */
+
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+# error "This code only works on little endian machines."
+#endif
+
+#include <cstdint>
+
+#include <protozero/exception.hpp>
+
+namespace protozero {
+
+/**
+ * The maximum length of a 64bit varint.
+ */
+const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
+
+// from https://github.com/facebook/folly/blob/master/folly/Varint.h
+/**
+ * Decode a 64bit varint.
+ *
+ * String exception guarantee: if there is an exception the data pointer will
+ * not be changed.
+ *
+ * @param[in,out] data Pointer to pointer to the input data. After the function
+ *        returns this will point to the next data to be read.
+ * @param[in] end Pointer one past the end of the input data.
+ * @returns The decoded integer
+ * @throws varint_too_long_exception if the varint is longer then the maximum
+ *         length that would fit in a 64bit int. Usually this means your data
+ *         is corrupted or you are trying to read something as a varint that
+ *         isn't.
+ * @throws end_of_buffer_exception if the *end* of the buffer was reached
+ *         before the end of the varint.
+ */
+inline uint64_t decode_varint(const char** data, const char* end) {
+    const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
+    const int8_t* iend = reinterpret_cast<const int8_t*>(end);
+    const int8_t* p = begin;
+    uint64_t val = 0;
+
+    if (iend - begin >= max_varint_length) {  // fast path
+        do {
+            int64_t b;
+            b = *p++; val  = uint64_t((b & 0x7f)      ); if (b >= 0) break;
+            b = *p++; val |= uint64_t((b & 0x7f) <<  7); if (b >= 0) break;
+            b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
+            b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
+            b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
+            b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
+            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;
+            throw varint_too_long_exception();
+        } while (false);
+    } else {
+        int shift = 0;
+        while (p != iend && *p < 0) {
+            val |= uint64_t(*p++ & 0x7f) << shift;
+            shift += 7;
+        }
+        if (p == iend) {
+            throw end_of_buffer_exception();
+        }
+        val |= uint64_t(*p++) << shift;
+    }
+
+    *data = reinterpret_cast<const char*>(p);
+    return val;
+}
+
+/**
+ * Varint-encode a 64bit integer.
+ */
+template <typename OutputIterator>
+inline int write_varint(OutputIterator data, uint64_t value) {
+    int n=1;
+
+    while (value >= 0x80) {
+        *data++ = char((value & 0x7f) | 0x80);
+        value >>= 7;
+        ++n;
+    }
+    *data++ = char(value);
+
+    return n;
+}
+
+/**
+ * ZigZag encodes a 32 bit integer.
+ */
+inline uint32_t encode_zigzag32(int32_t value) noexcept {
+    return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
+}
+
+/**
+ * ZigZag encodes a 64 bit integer.
+ */
+inline uint64_t encode_zigzag64(int64_t value) noexcept {
+    return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
+}
+
+/**
+ * Decodes a 32 bit ZigZag-encoded integer.
+ */
+inline int32_t decode_zigzag32(uint32_t value) noexcept {
+    return int32_t(value >> 1) ^ -int32_t(value & 1);
+}
+
+/**
+ * Decodes a 64 bit ZigZag-encoded integer.
+ */
+inline int64_t decode_zigzag64(uint64_t value) noexcept {
+    return int64_t(value >> 1) ^ -int64_t(value & 1);
+}
+
+} // end namespace protozero
+
+#endif // PROTOZERO_VARINT_HPP
diff --git a/include/osmium/io/pbf_input.hpp b/include/utf8.h
similarity index 65%
copy from include/osmium/io/pbf_input.hpp
copy to include/utf8.h
index 766153e..82b13f5 100644
--- a/include/osmium/io/pbf_input.hpp
+++ b/include/utf8.h
@@ -1,14 +1,6 @@
-#ifndef OSMIUM_IO_PBF_INPUT_HPP
-#define OSMIUM_IO_PBF_INPUT_HPP
+// Copyright 2006 Nemanja Trifunovic
 
 /*
-
-This file is part of Osmium (http://osmcode.org/libosmium).
-
-Copyright 2013-2015 Jochen Topf <jochen at topf.org> and others (see README).
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
 Permission is hereby granted, free of charge, to any person or organization
 obtaining a copy of the software and accompanying documentation covered by
 this license (the "Software") to use, reproduce, display, distribute,
@@ -30,20 +22,13 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE.
-
 */
 
-/**
- * @file
- *
- * Include this file if you want to read OSM PBF files.
- *
- * @attention If you include this file, you'll need to link with
- *            `libprotobuf-lite`, `libosmpbf`, `ws2_32` (Windows only),
- *            `libz`, and enable multithreading.
- */
 
-#include <osmium/io/reader.hpp> // IWYU pragma: export
-#include <osmium/io/detail/pbf_input_format.hpp> // IWYU pragma: export
+#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include "utf8/checked.h"
+#include "utf8/unchecked.h"
 
-#endif // OSMIUM_IO_PBF_INPUT_HPP
+#endif // header guard
diff --git a/include/utf8/checked.h b/include/utf8/checked.h
new file mode 100644
index 0000000..1331155
--- /dev/null
+++ b/include/utf8/checked.h
@@ -0,0 +1,327 @@
+// Copyright 2006 Nemanja Trifunovic
+
+/*
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+
+#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include "core.h"
+#include <stdexcept>
+
+namespace utf8
+{
+    // Base for the exceptions that may be thrown from the library
+    class exception : public ::std::exception {
+    };
+
+    // Exceptions that may be thrown from the library functions.
+    class invalid_code_point : public exception {
+        uint32_t cp;
+    public:
+        invalid_code_point(uint32_t cp) : cp(cp) {}
+        virtual const char* what() const throw() { return "Invalid code point"; }
+        uint32_t code_point() const {return cp;}
+    };
+
+    class invalid_utf8 : public exception {
+        uint8_t u8;
+    public:
+        invalid_utf8 (uint8_t u) : u8(u) {}
+        virtual const char* what() const throw() { return "Invalid UTF-8"; }
+        uint8_t utf8_octet() const {return u8;}
+    };
+
+    class invalid_utf16 : public exception {
+        uint16_t u16;
+    public:
+        invalid_utf16 (uint16_t u) : u16(u) {}
+        virtual const char* what() const throw() { return "Invalid UTF-16"; }
+        uint16_t utf16_word() const {return u16;}
+    };
+
+    class not_enough_room : public exception {
+    public:
+        virtual const char* what() const throw() { return "Not enough space"; }
+    };
+
+    /// The library API - functions intended to be called by the users
+
+    template <typename octet_iterator>
+    octet_iterator append(uint32_t cp, octet_iterator result)
+    {
+        if (!utf8::internal::is_code_point_valid(cp))
+            throw invalid_code_point(cp);
+
+        if (cp < 0x80)                        // one octet
+            *(result++) = static_cast<uint8_t>(cp);
+        else if (cp < 0x800) {                // two octets
+            *(result++) = static_cast<uint8_t>((cp >> 6)            | 0xc0);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        else if (cp < 0x10000) {              // three octets
+            *(result++) = static_cast<uint8_t>((cp >> 12)           | 0xe0);
+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        else {                                // four octets
+            *(result++) = static_cast<uint8_t>((cp >> 18)           | 0xf0);
+            *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)  | 0x80);
+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);
+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);
+        }
+        return result;
+    }
+
+    template <typename octet_iterator, typename output_iterator>
+    output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)
+    {
+        while (start != end) {
+            octet_iterator sequence_start = start;
+            internal::utf_error err_code = utf8::internal::validate_next(start, end);
+            switch (err_code) {
+                case internal::UTF8_OK :
+                    for (octet_iterator it = sequence_start; it != start; ++it)
+                        *out++ = *it;
+                    break;
+                case internal::NOT_ENOUGH_ROOM:
+                    throw not_enough_room();
+                case internal::INVALID_LEAD:
+                    out = utf8::append (replacement, out);
+                    ++start;
+                    break;
+                case internal::INCOMPLETE_SEQUENCE:
+                case internal::OVERLONG_SEQUENCE:
+                case internal::INVALID_CODE_POINT:
+                    out = utf8::append (replacement, out);
+                    ++start;
+                    // just one replacement mark for the sequence
+                    while (start != end && utf8::internal::is_trail(*start))
+                        ++start;
+                    break;
+            }
+        }
+        return out;
+    }
+
+    template <typename octet_iterator, typename output_iterator>
+    inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)
+    {
+        static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd);
+        return utf8::replace_invalid(start, end, out, replacement_marker);
+    }
+
+    template <typename octet_iterator>
+    uint32_t next(octet_iterator& it, octet_iterator end)
+    {
+        uint32_t cp = 0;
+        internal::utf_error err_code = utf8::internal::validate_next(it, end, cp);
+        switch (err_code) {
+            case internal::UTF8_OK :
+                break;
+            case internal::NOT_ENOUGH_ROOM :
+                throw not_enough_room();
+            case internal::INVALID_LEAD :
+            case internal::INCOMPLETE_SEQUENCE :
+            case internal::OVERLONG_SEQUENCE :
+                throw invalid_utf8(*it);
+            case internal::INVALID_CODE_POINT :
+                throw invalid_code_point(cp);
+        }
+        return cp;
+    }
+
+    template <typename octet_iterator>
+    uint32_t peek_next(octet_iterator it, octet_iterator end)
+    {
+        return utf8::next(it, end);
+    }
+
+    template <typename octet_iterator>
+    uint32_t prior(octet_iterator& it, octet_iterator start)
+    {
+        // can't do much if it == start
+        if (it == start)
+            throw not_enough_room();
+
+        octet_iterator end = it;
+        // Go back until we hit either a lead octet or start
+        while (utf8::internal::is_trail(*(--it)))
+            if (it == start)
+                throw invalid_utf8(*it); // error - no lead byte in the sequence
+        return utf8::peek_next(it, end);
+    }
+
+    /// Deprecated in versions that include "prior"
+    template <typename octet_iterator>
+    uint32_t previous(octet_iterator& it, octet_iterator pass_start)
+    {
+        octet_iterator end = it;
+        while (utf8::internal::is_trail(*(--it)))
+            if (it == pass_start)
+                throw invalid_utf8(*it); // error - no lead byte in the sequence
+        octet_iterator temp = it;
+        return utf8::next(temp, end);
+    }
+
+    template <typename octet_iterator, typename distance_type>
+    void advance (octet_iterator& it, distance_type n, octet_iterator end)
+    {
+        for (distance_type i = 0; i < n; ++i)
+            utf8::next(it, end);
+    }
+
+    template <typename octet_iterator>
+    typename std::iterator_traits<octet_iterator>::difference_type
+    distance (octet_iterator first, octet_iterator last)
+    {
+        typename std::iterator_traits<octet_iterator>::difference_type dist;
+        for (dist = 0; first < last; ++dist)
+            utf8::next(first, last);
+        return dist;
+    }
+
+    template <typename u16bit_iterator, typename octet_iterator>
+    octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
+    {
+        while (start != end) {
+            uint32_t cp = utf8::internal::mask16(*start++);
+            // Take care of surrogate pairs first
+            if (utf8::internal::is_lead_surrogate(cp)) {
+                if (start != end) {
+                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);
+                    if (utf8::internal::is_trail_surrogate(trail_surrogate))
+                        cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
+                    else
+                        throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));
+                }
+                else
+                    throw invalid_utf16(static_cast<uint16_t>(cp));
+
+            }
+            // Lone trail surrogate
+            else if (utf8::internal::is_trail_surrogate(cp))
+                throw invalid_utf16(static_cast<uint16_t>(cp));
+
+            result = utf8::append(cp, result);
+        }
+        return result;
+    }
+
+    template <typename u16bit_iterator, typename octet_iterator>
+    u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
+    {
+        while (start != end) {
+            uint32_t cp = utf8::next(start, end);
+            if (cp > 0xffff) { //make a surrogate pair
+                *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);
+                *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
+            }
+            else
+                *result++ = static_cast<uint16_t>(cp);
+        }
+        return result;
+    }
+
+    template <typename octet_iterator, typename u32bit_iterator>
+    octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)
+    {
+        while (start != end)
+            result = utf8::append(*(start++), result);
+
+        return result;
+    }
+
+    template <typename octet_iterator, typename u32bit_iterator>
+    u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
+    {
+        while (start != end)
+            (*result++) = utf8::next(start, end);
+
+        return result;
+    }
+
+    // The iterator class
+    template <typename octet_iterator>
+    class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
+      octet_iterator it;
+      octet_iterator range_start;
+      octet_iterator range_end;
+      public:
+      iterator () {}
+      explicit iterator (const octet_iterator& octet_it,
+                         const octet_iterator& range_start,
+                         const octet_iterator& range_end) :
+               it(octet_it), range_start(range_start), range_end(range_end)
+      {
+          if (it < range_start || it > range_end)
+              throw std::out_of_range("Invalid utf-8 iterator position");
+      }
+      // the default "big three" are OK
+      octet_iterator base () const { return it; }
+      uint32_t operator * () const
+      {
+          octet_iterator temp = it;
+          return utf8::next(temp, range_end);
+      }
+      bool operator == (const iterator& rhs) const
+      {
+          if (range_start != rhs.range_start || range_end != rhs.range_end)
+              throw std::logic_error("Comparing utf-8 iterators defined with different ranges");
+          return (it == rhs.it);
+      }
+      bool operator != (const iterator& rhs) const
+      {
+          return !(operator == (rhs));
+      }
+      iterator& operator ++ ()
+      {
+          utf8::next(it, range_end);
+          return *this;
+      }
+      iterator operator ++ (int)
+      {
+          iterator temp = *this;
+          utf8::next(it, range_end);
+          return temp;
+      }
+      iterator& operator -- ()
+      {
+          utf8::prior(it, range_start);
+          return *this;
+      }
+      iterator operator -- (int)
+      {
+          iterator temp = *this;
+          utf8::prior(it, range_start);
+          return temp;
+      }
+    }; // class iterator
+
+} // namespace utf8
+
+#endif //header guard
+
+
diff --git a/include/utf8/core.h b/include/utf8/core.h
new file mode 100644
index 0000000..693d388
--- /dev/null
+++ b/include/utf8/core.h
@@ -0,0 +1,329 @@
+// Copyright 2006 Nemanja Trifunovic
+
+/*
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+
+#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include <iterator>
+
+namespace utf8
+{
+    // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers
+    // You may need to change them to match your system.
+    // These typedefs have the same names as ones from cstdint, or boost/cstdint
+    typedef unsigned char   uint8_t;
+    typedef unsigned short  uint16_t;
+    typedef unsigned int    uint32_t;
+
+// Helper code - not intended to be directly called by the library users. May be changed at any time
+namespace internal
+{
+    // Unicode constants
+    // Leading (high) surrogates: 0xd800 - 0xdbff
+    // Trailing (low) surrogates: 0xdc00 - 0xdfff
+    const uint16_t LEAD_SURROGATE_MIN  = 0xd800u;
+    const uint16_t LEAD_SURROGATE_MAX  = 0xdbffu;
+    const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;
+    const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;
+    const uint16_t LEAD_OFFSET         = LEAD_SURROGATE_MIN - (0x10000 >> 10);
+    const uint32_t SURROGATE_OFFSET    = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;
+
+    // Maximum valid value for a Unicode code point
+    const uint32_t CODE_POINT_MAX      = 0x0010ffffu;
+
+    template<typename octet_type>
+    inline uint8_t mask8(octet_type oc)
+    {
+        return static_cast<uint8_t>(0xff & oc);
+    }
+    template<typename u16_type>
+    inline uint16_t mask16(u16_type oc)
+    {
+        return static_cast<uint16_t>(0xffff & oc);
+    }
+    template<typename octet_type>
+    inline bool is_trail(octet_type oc)
+    {
+        return ((utf8::internal::mask8(oc) >> 6) == 0x2);
+    }
+
+    template <typename u16>
+    inline bool is_lead_surrogate(u16 cp)
+    {
+        return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX);
+    }
+
+    template <typename u16>
+    inline bool is_trail_surrogate(u16 cp)
+    {
+        return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
+    }
+
+    template <typename u16>
+    inline bool is_surrogate(u16 cp)
+    {
+        return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
+    }
+
+    template <typename u32>
+    inline bool is_code_point_valid(u32 cp)
+    {
+        return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp));
+    }
+
+    template <typename octet_iterator>
+    inline typename std::iterator_traits<octet_iterator>::difference_type
+    sequence_length(octet_iterator lead_it)
+    {
+        uint8_t lead = utf8::internal::mask8(*lead_it);
+        if (lead < 0x80)
+            return 1;
+        else if ((lead >> 5) == 0x6)
+            return 2;
+        else if ((lead >> 4) == 0xe)
+            return 3;
+        else if ((lead >> 3) == 0x1e)
+            return 4;
+        else
+            return 0;
+    }
+
+    template <typename octet_difference_type>
+    inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length)
+    {
+        if (cp < 0x80) {
+            if (length != 1) 
+                return true;
+        }
+        else if (cp < 0x800) {
+            if (length != 2) 
+                return true;
+        }
+        else if (cp < 0x10000) {
+            if (length != 3) 
+                return true;
+        }
+
+        return false;
+    }
+
+    enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT};
+
+    /// Helper for get_sequence_x
+    template <typename octet_iterator>
+    utf_error increase_safely(octet_iterator& it, octet_iterator end)
+    {
+        if (++it == end)
+            return NOT_ENOUGH_ROOM;
+
+        if (!utf8::internal::is_trail(*it))
+            return INCOMPLETE_SEQUENCE;
+        
+        return UTF8_OK;
+    }
+
+    #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;}    
+
+    /// get_sequence_x functions decode utf-8 sequences of the length x
+    template <typename octet_iterator>
+    utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point)
+    {
+        if (it == end)
+            return NOT_ENOUGH_ROOM;
+
+        code_point = utf8::internal::mask8(*it);
+
+        return UTF8_OK;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point)
+    {
+        if (it == end) 
+            return NOT_ENOUGH_ROOM;
+        
+        code_point = utf8::internal::mask8(*it);
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f);
+
+        return UTF8_OK;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point)
+    {
+        if (it == end)
+            return NOT_ENOUGH_ROOM;
+            
+        code_point = utf8::internal::mask8(*it);
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point += (*it) & 0x3f;
+
+        return UTF8_OK;
+    }
+
+    template <typename octet_iterator>
+    utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point)
+    {
+        if (it == end)
+           return NOT_ENOUGH_ROOM;
+
+        code_point = utf8::internal::mask8(*it);
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point += (utf8::internal::mask8(*it) << 6) & 0xfff;
+
+        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
+
+        code_point += (*it) & 0x3f;
+
+        return UTF8_OK;
+    }
+
+    #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR
+
+    template <typename octet_iterator>
+    utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point)
+    {
+        // Save the original value of it so we can go back in case of failure
+        // Of course, it does not make much sense with i.e. stream iterators
+        octet_iterator original_it = it;
+
+        uint32_t cp = 0;
+        // Determine the sequence length based on the lead octet
+        typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;
+        const octet_difference_type length = utf8::internal::sequence_length(it);
+
+        // Get trail octets and calculate the code point
+        utf_error err = UTF8_OK;
+        switch (length) {
+            case 0: 
+                return INVALID_LEAD;
+            case 1:
+                err = utf8::internal::get_sequence_1(it, end, cp);
+                break;
+            case 2:
+                err = utf8::internal::get_sequence_2(it, end, cp);
+            break;
+            case 3:
+                err = utf8::internal::get_sequence_3(it, end, cp);
+            break;
+            case 4:
+                err = utf8::internal::get_sequence_4(it, end, cp);
+            break;
+        }
+
+        if (err == UTF8_OK) {
+            // Decoding succeeded. Now, security checks...
+            if (utf8::internal::is_code_point_valid(cp)) {
+                if (!utf8::internal::is_overlong_sequence(cp, length)){
+                    // Passed! Return here.
+                    code_point = cp;
+                    ++it;
+                    return UTF8_OK;
+                }
+                else
+                    err = OVERLONG_SEQUENCE;
+            }
+            else 
+                err = INVALID_CODE_POINT;
+        }
+
+        // Failure branch - restore the original value of the iterator
+        it = original_it;
+        return err;
+    }
+
+    template <typename octet_iterator>
+    inline utf_error validate_next(octet_iterator& it, octet_iterator end) {
+        uint32_t ignored;
+        return utf8::internal::validate_next(it, end, ignored);
+    }
+
+} // namespace internal
+
+    /// The library API - functions intended to be called by the users
+
+    // Byte order mark
+    const uint8_t bom[] = {0xef, 0xbb, 0xbf};
+
+    template <typename octet_iterator>
+    octet_iterator find_invalid(octet_iterator start, octet_iterator end)
+    {
+        octet_iterator result = start;
+        while (result != end) {
+            utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end);
+            if (err_code != internal::UTF8_OK)
+                return result;
+        }
+        return result;
+    }
+
+    template <typename octet_iterator>
+    inline bool is_valid(octet_iterator start, octet_iterator end)
+    {
+        return (utf8::find_invalid(start, end) == end);
+    }
+
+    template <typename octet_iterator>
+    inline bool starts_with_bom (octet_iterator it, octet_iterator end)
+    {
+        return (
+            ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) &&
+            ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) &&
+            ((it != end) && (utf8::internal::mask8(*it))   == bom[2])
+           );
+    }
+	
+    //Deprecated in release 2.3 
+    template <typename octet_iterator>
+    inline bool is_bom (octet_iterator it)
+    {
+        return (
+            (utf8::internal::mask8(*it++)) == bom[0] &&
+            (utf8::internal::mask8(*it++)) == bom[1] &&
+            (utf8::internal::mask8(*it))   == bom[2]
+           );
+    }
+} // namespace utf8
+
+#endif // header guard
+
+
diff --git a/include/utf8/unchecked.h b/include/utf8/unchecked.h
new file mode 100644
index 0000000..cb24271
--- /dev/null
+++ b/include/utf8/unchecked.h
@@ -0,0 +1,228 @@
+// Copyright 2006 Nemanja Trifunovic
+
+/*
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+
+#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
+
+#include "core.h"
+
+namespace utf8
+{
+    namespace unchecked 
+    {
+        template <typename octet_iterator>
+        octet_iterator append(uint32_t cp, octet_iterator result)
+        {
+            if (cp < 0x80)                        // one octet
+                *(result++) = static_cast<uint8_t>(cp);  
+            else if (cp < 0x800) {                // two octets
+                *(result++) = static_cast<uint8_t>((cp >> 6)          | 0xc0);
+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);
+            }
+            else if (cp < 0x10000) {              // three octets
+                *(result++) = static_cast<uint8_t>((cp >> 12)         | 0xe0);
+                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);
+            }
+            else {                                // four octets
+                *(result++) = static_cast<uint8_t>((cp >> 18)         | 0xf0);
+                *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)| 0x80);
+                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);
+            }
+            return result;
+        }
+
+        template <typename octet_iterator>
+        uint32_t next(octet_iterator& it)
+        {
+            uint32_t cp = utf8::internal::mask8(*it);
+            typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);
+            switch (length) {
+                case 1:
+                    break;
+                case 2:
+                    it++;
+                    cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
+                    break;
+                case 3:
+                    ++it; 
+                    cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);
+                    ++it;
+                    cp += (*it) & 0x3f;
+                    break;
+                case 4:
+                    ++it;
+                    cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);                
+                    ++it;
+                    cp += (utf8::internal::mask8(*it) << 6) & 0xfff;
+                    ++it;
+                    cp += (*it) & 0x3f; 
+                    break;
+            }
+            ++it;
+            return cp;        
+        }
+
+        template <typename octet_iterator>
+        uint32_t peek_next(octet_iterator it)
+        {
+            return utf8::unchecked::next(it);    
+        }
+
+        template <typename octet_iterator>
+        uint32_t prior(octet_iterator& it)
+        {
+            while (utf8::internal::is_trail(*(--it))) ;
+            octet_iterator temp = it;
+            return utf8::unchecked::next(temp);
+        }
+
+        // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous)
+        template <typename octet_iterator>
+        inline uint32_t previous(octet_iterator& it)
+        {
+            return utf8::unchecked::prior(it);
+        }
+
+        template <typename octet_iterator, typename distance_type>
+        void advance (octet_iterator& it, distance_type n)
+        {
+            for (distance_type i = 0; i < n; ++i)
+                utf8::unchecked::next(it);
+        }
+
+        template <typename octet_iterator>
+        typename std::iterator_traits<octet_iterator>::difference_type
+        distance (octet_iterator first, octet_iterator last)
+        {
+            typename std::iterator_traits<octet_iterator>::difference_type dist;
+            for (dist = 0; first < last; ++dist) 
+                utf8::unchecked::next(first);
+            return dist;
+        }
+
+        template <typename u16bit_iterator, typename octet_iterator>
+        octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
+        {       
+            while (start != end) {
+                uint32_t cp = utf8::internal::mask16(*start++);
+            // Take care of surrogate pairs first
+                if (utf8::internal::is_lead_surrogate(cp)) {
+                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);
+                    cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
+                }
+                result = utf8::unchecked::append(cp, result);
+            }
+            return result;         
+        }
+
+        template <typename u16bit_iterator, typename octet_iterator>
+        u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
+        {
+            while (start < end) {
+                uint32_t cp = utf8::unchecked::next(start);
+                if (cp > 0xffff) { //make a surrogate pair
+                    *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);
+                    *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
+                }
+                else
+                    *result++ = static_cast<uint16_t>(cp);
+            }
+            return result;
+        }
+
+        template <typename octet_iterator, typename u32bit_iterator>
+        octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)
+        {
+            while (start != end)
+                result = utf8::unchecked::append(*(start++), result);
+
+            return result;
+        }
+
+        template <typename octet_iterator, typename u32bit_iterator>
+        u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
+        {
+            while (start < end)
+                (*result++) = utf8::unchecked::next(start);
+
+            return result;
+        }
+
+        // The iterator class
+        template <typename octet_iterator>
+          class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { 
+            octet_iterator it;
+            public:
+            iterator () {}
+            explicit iterator (const octet_iterator& octet_it): it(octet_it) {}
+            // the default "big three" are OK
+            octet_iterator base () const { return it; }
+            uint32_t operator * () const
+            {
+                octet_iterator temp = it;
+                return utf8::unchecked::next(temp);
+            }
+            bool operator == (const iterator& rhs) const 
+            { 
+                return (it == rhs.it);
+            }
+            bool operator != (const iterator& rhs) const
+            {
+                return !(operator == (rhs));
+            }
+            iterator& operator ++ () 
+            {
+                ::std::advance(it, utf8::internal::sequence_length(it));
+                return *this;
+            }
+            iterator operator ++ (int)
+            {
+                iterator temp = *this;
+                ::std::advance(it, utf8::internal::sequence_length(it));
+                return temp;
+            }  
+            iterator& operator -- ()
+            {
+                utf8::unchecked::prior(it);
+                return *this;
+            }
+            iterator operator -- (int)
+            {
+                iterator temp = *this;
+                utf8::unchecked::prior(it);
+                return temp;
+            }
+          }; // class iterator
+
+    } // namespace utf8::unchecked
+} // namespace utf8 
+
+
+#endif // header guard
+
diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh
index 528e860..119e9fd 100755
--- a/scripts/travis_install.sh
+++ b/scripts/travis_install.sh
@@ -3,14 +3,11 @@
 #  travis_install.sh
 #
 
-if [ "$TRAVIS_OS_NAME" = "linux" ]; then
+if [ "$TRAVIS_OS_NAME" = "osx" ]; then
 
-    # install dependencies
-    sudo apt-get install --yes make libgdal-dev
+    brew install google-sparsehash || true
 
-elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
-
-    brew install protobuf osm-pbf google-sparsehash || true
+    brew install --without-python boost || true
 
     # workaround for gdal homebrew problem
     brew remove gdal
@@ -20,7 +17,4 @@ fi
 
 cd ..
 git clone --quiet --depth 1 https://github.com/osmcode/osm-testdata.git
-git clone --quiet --depth 1 https://github.com/scrosby/OSM-binary.git
-cd OSM-binary/src
-make
 
diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh
index c0f511e..75b3b36 100755
--- a/scripts/travis_script.sh
+++ b/scripts/travis_script.sh
@@ -21,8 +21,6 @@ fi
 
 cmake -LA \
     -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
-    -DOSMPBF_INCLUDE_DIR=${TRAVIS_BUILD_DIR}/../OSM-binary/include \
-    -DOSMPBF_LIBRARY=${TRAVIS_BUILD_DIR}/../OSM-binary/src/libosmpbf.a \
     ${WORKAROUND} \
     ..
 
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 02e2433..0047457 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -15,6 +15,11 @@ add_library(testlib STATIC test_main.cpp)
 
 set(ALL_TESTS "")
 
+# Otherwise GCC throws a lot of warnings for REQUIRE(...) from Catch v.1.2.1
+if(CMAKE_COMPILER_IS_GNUCXX)
+    add_definitions(-Wno-parentheses)
+endif()
+
 
 #-----------------------------------------------------------------------------
 #
@@ -90,6 +95,7 @@ add_unit_test(area test_node_ref_segment)
 
 add_unit_test(basic test_box)
 add_unit_test(basic test_changeset)
+add_unit_test(basic test_crc)
 add_unit_test(basic test_entity_bits)
 add_unit_test(basic test_location)
 add_unit_test(basic test_node)
@@ -129,6 +135,7 @@ add_unit_test(io test_bzip2 ENABLE_IF ${BZIP2_FOUND} LIBS ${BZIP2_LIBRARIES})
 add_unit_test(io test_file_formats)
 add_unit_test(io test_reader LIBS "${OSMIUM_XML_LIBRARIES}")
 add_unit_test(io test_output_iterator ENABLE_IF ${Threads_FOUND} LIBS ${CMAKE_THREAD_LIBS_INIT})
+add_unit_test(io test_string_table)
 
 add_unit_test(tags test_filter)
 add_unit_test(tags test_operators)
diff --git a/test/data-tests/testdata-testcases.cpp b/test/data-tests/testdata-testcases.cpp
index 6ed6ae9..0ea7fc8 100644
--- a/test/data-tests/testdata-testcases.cpp
+++ b/test/data-tests/testdata-testcases.cpp
@@ -6,8 +6,6 @@
 
 #include "testdata-testcases.hpp"
 
-#include <osmpbf/osmpbf.h>
-
 std::string dirname;
 
 int main(int argc, char* argv[]) {
diff --git a/test/data-tests/testdata-xml.cpp b/test/data-tests/testdata-xml.cpp
index 1f846ef..8102759 100644
--- a/test/data-tests/testdata-xml.cpp
+++ b/test/data-tests/testdata-xml.cpp
@@ -12,6 +12,10 @@
 #include <osmium/io/gzip_compression.hpp>
 #include <osmium/visitor.hpp>
 
+std::string S_(const char* s) {
+    return std::string(s);
+}
+
 std::string filename(const char* test_id, const char* suffix = "osm") {
     const char* testdir = getenv("TESTDIR");
     if (!testdir) {
@@ -338,25 +342,25 @@ TEST_CASE("Reading OSM XML 140") {
             auto len = atoi(t["unicode_utf8_length"]);
             REQUIRE(len == strlen(uc));
 
-            REQUIRE(!strcmp(uc, t["unicode_xml"]));
+            REQUIRE(S_(uc) == t["unicode_xml"]);
 
 // workaround for missing support for u8 string literals on Windows
 #if !defined(_MSC_VER)
             switch (count) {
                 case 1:
-                    REQUIRE(!strcmp(uc, u8"a"));
+                    REQUIRE(S_(uc) ==  u8"a");
                     break;
                 case 2:
-                    REQUIRE(!strcmp(uc, u8"\u00e4"));
+                    REQUIRE(S_(uc) == u8"\u00e4");
                     break;
                 case 3:
-                    REQUIRE(!strcmp(uc, u8"\u30dc"));
+                    REQUIRE(S_(uc) == u8"\u30dc");
                     break;
                 case 4:
-                    REQUIRE(!strcmp(uc, u8"\U0001d11e"));
+                    REQUIRE(S_(uc) == u8"\U0001d11e");
                     break;
                 case 5:
-                    REQUIRE(!strcmp(uc, u8"\U0001f6eb"));
+                    REQUIRE(S_(uc) == u8"\U0001f6eb");
                     break;
                 default:
                     REQUIRE(false); // should not be here
@@ -383,11 +387,100 @@ TEST_CASE("Reading OSM XML 141") {
         const osmium::Node& node = buffer.get<osmium::Node>(0);
         const osmium::TagList& tags = node.tags();
 
-        REQUIRE(!strcmp(tags["less-than"],    "<"));
-        REQUIRE(!strcmp(tags["greater-than"], ">"));
-        REQUIRE(!strcmp(tags["apostrophe"],   "'"));
-        REQUIRE(!strcmp(tags["ampersand"],    "&"));
-        REQUIRE(!strcmp(tags["quote"],        "\""));
+        REQUIRE(S_(tags["less-than"])    == "<");
+        REQUIRE(S_(tags["greater-than"]) == ">");
+        REQUIRE(S_(tags["apostrophe"])   == "'");
+        REQUIRE(S_(tags["ampersand"])    == "&");
+        REQUIRE(S_(tags["quote"])        == "\"");
+    }
+
+}
+
+
+// =============================================
+
+TEST_CASE("Reading OSM XML 142") {
+
+    SECTION("Using Reader to read nodes") {
+        osmium::io::Reader reader(filename("142-whitespace"));
+        osmium::memory::Buffer buffer = reader.read();
+        reader.close();
+
+        int count = 0;
+        for (auto it = buffer.begin<osmium::Node>(); it != buffer.end<osmium::Node>(); ++it) {
+            ++count;
+            REQUIRE(it->id() == count);
+            REQUIRE(it->tags().size() == 1);
+            const osmium::Tag& tag = *(it->tags().begin());
+
+            switch (count) {
+                case 1:
+                    REQUIRE(S_(it->user()) == "user name");
+                    REQUIRE(S_(tag.key()) == "key with space");
+                    REQUIRE(S_(tag.value()) == "value with space");
+                    break;
+                case 2:
+                    REQUIRE(S_(it->user()) == "line\nfeed");
+                    REQUIRE(S_(tag.key()) == "key with\nlinefeed");
+                    REQUIRE(S_(tag.value()) == "value with\nlinefeed");
+                    break;
+                case 3:
+                    REQUIRE(S_(it->user()) == "carriage\rreturn");
+                    REQUIRE(S_(tag.key()) == "key with\rcarriage\rreturn");
+                    REQUIRE(S_(tag.value()) == "value with\rcarriage\rreturn");
+                    break;
+                case 4:
+                    REQUIRE(S_(it->user()) == "tab\tulator");
+                    REQUIRE(S_(tag.key()) == "key with\ttab");
+                    REQUIRE(S_(tag.value()) == "value with\ttab");
+                    break;
+                case 5:
+                    REQUIRE(S_(it->user()) == "unencoded linefeed");
+                    REQUIRE(S_(tag.key()) == "key with unencoded linefeed");
+                    REQUIRE(S_(tag.value()) == "value with unencoded linefeed");
+                    break;
+                default:
+                    REQUIRE(false); // should not be here
+            }
+        }
+        REQUIRE(count == 5);
+    }
+
+    SECTION("Using Reader to read relation") {
+        osmium::io::Reader reader(filename("142-whitespace"));
+        osmium::memory::Buffer buffer = reader.read();
+        reader.close();
+
+        auto it = buffer.begin<osmium::Relation>();
+        REQUIRE(it != buffer.end<osmium::Relation>());
+        REQUIRE(it->id() == 21);
+        const auto& members = it->members();
+        REQUIRE(members.size() == 5);
+
+        int count = 0;
+        for (const auto& member : members) {
+            ++count;
+            switch (count) {
+                case 1:
+                    REQUIRE(S_(member.role()) == "role with whitespace");
+                    break;
+                case 2:
+                    REQUIRE(S_(member.role()) == "role with\nlinefeed");
+                    break;
+                case 3:
+                    REQUIRE(S_(member.role()) == "role with\rcarriage\rreturn");
+                    break;
+                case 4:
+                    REQUIRE(S_(member.role()) == "role with\ttab");
+                    break;
+                case 5:
+                    REQUIRE(S_(member.role()) == "role with unencoded linefeed");
+                    break;
+                default:
+                    REQUIRE(false); // should not be here
+            }
+        }
+        REQUIRE(count == 5);
     }
 
 }
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index bb87af2..73abfe8 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,10 +1,6 @@
-
-// This is needed for Windows
-#define CATCH_CONFIG_CPP11_NULLPTR
-
 /*
- *  CATCH v1.0 build 53 (master branch)
- *  Generated: 2014-08-20 08:08:19.533804
+ *  Catch v1.2.1
+ *  Generated: 2015-06-30 18:23:27.961086
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -17,33 +13,43 @@
 
 #define TWOBLUECUBES_CATCH_HPP_INCLUDED
 
+#ifdef __clang__
+#    pragma clang system_header
+#elif defined __GNUC__
+#    pragma GCC system_header
+#endif
+
 // #included from: internal/catch_suppress_warnings.h
 
 #define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED
 
 #ifdef __clang__
-#pragma clang diagnostic ignored "-Wglobal-constructors"
-#pragma clang diagnostic ignored "-Wvariadic-macros"
-#pragma clang diagnostic ignored "-Wc99-extensions"
-#pragma clang diagnostic ignored "-Wunused-variable"
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wpadded"
-#pragma clang diagnostic ignored "-Wc++98-compat"
-#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#   ifdef __ICC // icpc defines the __clang__ macro
+#       pragma warning(push)
+#       pragma warning(disable: 161 1682)
+#   else // __ICC
+#       pragma clang diagnostic ignored "-Wglobal-constructors"
+#       pragma clang diagnostic ignored "-Wvariadic-macros"
+#       pragma clang diagnostic ignored "-Wc99-extensions"
+#       pragma clang diagnostic ignored "-Wunused-variable"
+#       pragma clang diagnostic push
+#       pragma clang diagnostic ignored "-Wpadded"
+#       pragma clang diagnostic ignored "-Wc++98-compat"
+#       pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#       pragma clang diagnostic ignored "-Wswitch-enum"
+#    endif
 #elif defined __GNUC__
-#pragma GCC diagnostic ignored "-Wvariadic-macros"
-#pragma GCC diagnostic ignored "-Wunused-variable"
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpadded"
-#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
-#pragma GCC diagnostic ignored "-Wsign-promo"
+#    pragma GCC diagnostic ignored "-Wvariadic-macros"
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wpadded"
 #endif
 
-#ifdef CATCH_CONFIG_MAIN
-#  define CATCH_CONFIG_RUNNER
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+#  define CATCH_IMPL
 #endif
 
-#ifdef CATCH_CONFIG_RUNNER
+#ifdef CATCH_IMPL
 #  ifndef CLARA_CONFIG_MAIN
 #    define CLARA_CONFIG_MAIN_NOT_DEFINED
 #    define CLARA_CONFIG_MAIN
@@ -70,16 +76,34 @@
 // #included from: catch_compiler_capabilities.h
 #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
 
-// Much of the following code is based on Boost (1.53)
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
+// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
+
+// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
 
 #ifdef __clang__
 
 #  if __has_feature(cxx_nullptr)
-#    define CATCH_CONFIG_CPP11_NULLPTR
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #  endif
 
 #  if __has_feature(cxx_noexcept)
-#    define CATCH_CONFIG_CPP11_NOEXCEPT
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 #  endif
 
 #endif // __clang__
@@ -88,51 +112,26 @@
 // Borland
 #ifdef __BORLANDC__
 
-#if (__BORLANDC__ > 0x582 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __BORLANDC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // EDG
 #ifdef __EDG_VERSION__
 
-#if (__EDG_VERSION__ > 238 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __EDG_VERSION__
 
 ////////////////////////////////////////////////////////////////////////////////
 // Digital Mars
 #ifdef __DMC__
 
-#if (__DMC__ > 0x840 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __DMC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // GCC
 #ifdef __GNUC__
 
-#if __GNUC__ < 3
-
-#if (__GNUC_MINOR__ >= 96 )
-//#define CATCH_CONFIG_SFINAE
-#endif
-
-#elif __GNUC__ >= 3
-
-// #define CATCH_CONFIG_SFINAE // Taking this out completely for now
-
-#endif // __GNUC__ < 3
-
 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) )
-
-#define CATCH_CONFIG_CPP11_NULLPTR
+#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #endif
 
 #endif // __GNUC__
@@ -141,8 +140,13 @@
 // Visual C++
 #ifdef _MSC_VER
 
-#if (_MSC_VER >= 1310 ) // (VC++ 7.0+)
-//#define CATCH_CONFIG_SFINAE // Not confirmed
+#if (_MSC_VER >= 1600)
+#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 #endif
 
 #endif // _MSC_VER
@@ -153,21 +157,62 @@
     ( defined __GNUC__ && __GNUC__ >= 3 ) || \
     ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
 
-#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS
-#define CATCH_CONFIG_VARIADIC_MACROS
-#endif
+#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
 
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
 // C++ language feature support
 
-// detect language version:
-#if (__cplusplus == 201103L)
-#  define CATCH_CPP11
-#  define CATCH_CPP11_OR_GREATER
-#elif (__cplusplus >= 201103L)
+// catch all support for C++11
+#if (__cplusplus >= 201103L)
+
 #  define CATCH_CPP11_OR_GREATER
+
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR)
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#    define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#    define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#    define CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#    define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#  endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_IS_ENUM
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_TUPLE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
+#define CATCH_CONFIG_VARIADIC_MACROS
 #endif
 
 // noexcept support:
@@ -182,8 +227,16 @@
 namespace Catch {
 
     class NonCopyable {
-        NonCopyable( NonCopyable const& );
-        void operator = ( NonCopyable const& );
+#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        NonCopyable( NonCopyable const& )              = delete;
+        NonCopyable( NonCopyable && )                  = delete;
+        NonCopyable& operator = ( NonCopyable const& ) = delete;
+        NonCopyable& operator = ( NonCopyable && )     = delete;
+#else
+        NonCopyable( NonCopyable const& info );
+        NonCopyable& operator = ( NonCopyable const& );
+#endif
+
     protected:
         NonCopyable() {}
         virtual ~NonCopyable();
@@ -221,6 +274,7 @@ namespace Catch {
     void toLowerInPlace( std::string& s );
     std::string toLower( std::string const& s );
     std::string trim( std::string const& str );
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
 
     struct pluralise {
         pluralise( std::size_t count, std::string const& label );
@@ -236,13 +290,14 @@ namespace Catch {
         SourceLineInfo();
         SourceLineInfo( char const* _file, std::size_t _line );
         SourceLineInfo( SourceLineInfo const& other );
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         SourceLineInfo( SourceLineInfo && )                  = default;
         SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
         SourceLineInfo& operator = ( SourceLineInfo && )     = default;
 #  endif
         bool empty() const;
         bool operator == ( SourceLineInfo const& other ) const;
+        bool operator < ( SourceLineInfo const& other ) const;
 
         std::string file;
         std::size_t line;
@@ -473,7 +528,7 @@ namespace Catch {
     struct ITestCaseRegistry {
         virtual ~ITestCaseRegistry();
         virtual std::vector<TestCase> const& getAllTests() const = 0;
-        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases ) const = 0;
+        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const = 0;
 
     };
 }
@@ -609,7 +664,9 @@ namespace Catch {
         Exception = 0x100 | FailureBit,
 
         ThrewException = Exception | 1,
-        DidntThrowException = Exception | 2
+        DidntThrowException = Exception | 2,
+
+        FatalErrorCondition = 0x200 | FailureBit
 
     }; };
 
@@ -622,11 +679,11 @@ namespace Catch {
 
     // ResultDisposition::Flags enum
     struct ResultDisposition { enum Flags {
-        Normal = 0x00,
+        Normal = 0x01,
 
-        ContinueOnFailure = 0x01,   // Failures fail test, but execution continues
-        FalseTest = 0x02,           // Prefix expression with !
-        SuppressFail = 0x04         // Failures are reported but do not fail the test
+        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
+        FalseTest = 0x04,           // Prefix expression with !
+        SuppressFail = 0x08         // Failures are reported but do not fail the test
     }; };
 
     inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
@@ -674,7 +731,7 @@ namespace Catch {
         AssertionResult();
         AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
         ~AssertionResult();
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
          AssertionResult( AssertionResult const& )              = default;
          AssertionResult( AssertionResult && )                  = default;
          AssertionResult& operator = ( AssertionResult const& ) = default;
@@ -730,8 +787,8 @@ namespace Catch {
                         ResultDisposition::Flags resultDisposition );
 
         template<typename T>
-        ExpressionLhs<T const&> operator->* ( T const& operand );
-        ExpressionLhs<bool> operator->* ( bool value );
+        ExpressionLhs<T const&> operator <= ( T const& operand );
+        ExpressionLhs<bool> operator <= ( bool value );
 
         template<typename T>
         ResultBuilder& operator << ( T const& value ) {
@@ -954,40 +1011,6 @@ namespace Internal {
 // #included from: catch_tostring.h
 #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
 
-// #included from: catch_sfinae.hpp
-#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED
-
-// Try to detect if the current compiler supports SFINAE
-
-namespace Catch {
-
-    struct TrueType {
-        static const bool value = true;
-        typedef void Enable;
-        char sizer[1];
-    };
-    struct FalseType {
-        static const bool value = false;
-        typedef void Disable;
-        char sizer[2];
-    };
-
-#ifdef CATCH_CONFIG_SFINAE
-
-    template<bool> struct NotABooleanExpression;
-
-    template<bool c> struct If : NotABooleanExpression<c> {};
-    template<> struct If<true> : TrueType {};
-    template<> struct If<false> : FalseType {};
-
-    template<int size> struct SizedIf;
-    template<> struct SizedIf<sizeof(TrueType)> : TrueType {};
-    template<> struct SizedIf<sizeof(FalseType)> : FalseType {};
-
-#endif // CATCH_CONFIG_SFINAE
-
-} // end namespace Catch
-
 #include <sstream>
 #include <iomanip>
 #include <limits>
@@ -1040,35 +1063,59 @@ inline id performOptionalSelector( id obj, SEL sel ) {
 
 #endif
 
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+#include <tuple>
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_IS_ENUM
+#include <type_traits>
+#endif
+
 namespace Catch {
-namespace Detail {
 
-// SFINAE is currently disabled by default for all compilers.
-// If the non SFINAE version of IsStreamInsertable is ambiguous for you
-// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE
-#ifdef CATCH_CONFIG_SFINAE
+// Why we're here.
+template<typename T>
+std::string toString( T const& value );
 
-    template<typename T>
-    class IsStreamInsertableHelper {
-        template<int N> struct TrueIfSizeable : TrueType {};
+// Built in overloads
+
+std::string toString( std::string const& value );
+std::string toString( std::wstring const& value );
+std::string toString( const char* const value );
+std::string toString( char* const value );
+std::string toString( const wchar_t* const value );
+std::string toString( wchar_t* const value );
+std::string toString( int value );
+std::string toString( unsigned long value );
+std::string toString( unsigned int value );
+std::string toString( const double value );
+std::string toString( const float value );
+std::string toString( bool value );
+std::string toString( char value );
+std::string toString( signed char value );
+std::string toString( unsigned char value );
 
-        template<typename T2>
-        static TrueIfSizeable<sizeof((*(std::ostream*)0) << *((T2 const*)0))> dummy(T2*);
-        static FalseType dummy(...);
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t );
+#endif
 
-    public:
-        typedef SizedIf<sizeof(dummy((T*)0))> type;
-    };
+#ifdef __OBJC__
+    std::string toString( NSString const * const& nsstring );
+    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+    std::string toString( NSObject* const& nsObject );
+#endif
 
-    template<typename T>
-    struct IsStreamInsertable : IsStreamInsertableHelper<T>::type {};
+namespace Detail {
 
-#else
+    extern std::string unprintableString;
 
     struct BorgType {
         template<typename T> BorgType( T const& );
     };
 
+    struct TrueType { char sizer[1]; };
+    struct FalseType { char sizer[2]; };
+
     TrueType& testStreamable( std::ostream& );
     FalseType testStreamable( FalseType );
 
@@ -1081,12 +1128,38 @@ namespace Detail {
         enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
     };
 
-#endif
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+    template<typename T,
+             bool IsEnum = std::is_enum<T>::value
+             >
+    struct EnumStringMaker
+    {
+        static std::string convert( T const& ) { return unprintableString; }
+    };
 
+    template<typename T>
+    struct EnumStringMaker<T,true>
+    {
+        static std::string convert( T const& v )
+        {
+            return ::Catch::toString(
+                static_cast<typename std::underlying_type<T>::type>(v)
+                );
+        }
+    };
+#endif
     template<bool C>
     struct StringMakerBase {
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+        template<typename T>
+        static std::string convert( T const& v )
+        {
+            return EnumStringMaker<T>::convert( v );
+        }
+#else
         template<typename T>
-        static std::string convert( T const& ) { return "{?}"; }
+        static std::string convert( T const& ) { return unprintableString; }
+#endif
     };
 
     template<>
@@ -1109,9 +1182,6 @@ namespace Detail {
 } // end namespace Detail
 
 template<typename T>
-std::string toString( T const& value );
-
-template<typename T>
 struct StringMaker :
     Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
 
@@ -1141,12 +1211,59 @@ namespace Detail {
     std::string rangeToString( InputIterator first, InputIterator last );
 }
 
+//template<typename T, typename Allocator>
+//struct StringMaker<std::vector<T, Allocator> > {
+//    static std::string convert( std::vector<T,Allocator> const& v ) {
+//        return Detail::rangeToString( v.begin(), v.end() );
+//    }
+//};
+
 template<typename T, typename Allocator>
-struct StringMaker<std::vector<T, Allocator> > {
-    static std::string convert( std::vector<T,Allocator> const& v ) {
-        return Detail::rangeToString( v.begin(), v.end() );
+std::string toString( std::vector<T,Allocator> const& v ) {
+    return Detail::rangeToString( v.begin(), v.end() );
+}
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+
+// toString for tuples
+namespace TupleDetail {
+  template<
+      typename Tuple,
+      std::size_t N = 0,
+      bool = (N < std::tuple_size<Tuple>::value)
+      >
+  struct ElementPrinter {
+      static void print( const Tuple& tuple, std::ostream& os )
+      {
+          os << ( N ? ", " : " " )
+             << Catch::toString(std::get<N>(tuple));
+          ElementPrinter<Tuple,N+1>::print(tuple,os);
+      }
+  };
+
+  template<
+      typename Tuple,
+      std::size_t N
+      >
+  struct ElementPrinter<Tuple,N,false> {
+      static void print( const Tuple&, std::ostream& ) {}
+  };
+
+}
+
+template<typename ...Types>
+struct StringMaker<std::tuple<Types...>> {
+
+    static std::string convert( const std::tuple<Types...>& tuple )
+    {
+        std::ostringstream os;
+        os << '{';
+        TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
+        os << " }";
+        return os.str();
     }
 };
+#endif // CATCH_CONFIG_CPP11_TUPLE
 
 namespace Detail {
     template<typename T>
@@ -1167,44 +1284,15 @@ std::string toString( T const& value ) {
     return StringMaker<T>::convert( value );
 }
 
-// Built in overloads
-
-std::string toString( std::string const& value );
-std::string toString( std::wstring const& value );
-std::string toString( const char* const value );
-std::string toString( char* const value );
-std::string toString( const wchar_t* const value );
-std::string toString( wchar_t* const value );
-std::string toString( int value );
-std::string toString( unsigned long value );
-std::string toString( unsigned int value );
-std::string toString( const double value );
-std::string toString( const float value );
-std::string toString( bool value );
-std::string toString( char value );
-std::string toString( signed char value );
-std::string toString( unsigned char value );
-
-#ifdef CATCH_CONFIG_CPP11_NULLPTR
-std::string toString( std::nullptr_t );
-#endif
-
-#ifdef __OBJC__
-    std::string toString( NSString const * const& nsstring );
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
-    std::string toString( NSObject* const& nsObject );
-#endif
-
     namespace Detail {
     template<typename InputIterator>
     std::string rangeToString( InputIterator first, InputIterator last ) {
         std::ostringstream oss;
         oss << "{ ";
         if( first != last ) {
-            oss << toString( *first );
-            for( ++first ; first != last ; ++first ) {
-                oss << ", " << toString( *first );
-            }
+            oss << Catch::toString( *first );
+            for( ++first ; first != last ; ++first )
+                oss << ", " << Catch::toString( *first );
         }
         oss << " }";
         return oss.str();
@@ -1220,13 +1308,13 @@ namespace Catch {
 template<typename T>
 class ExpressionLhs {
     ExpressionLhs& operator = ( ExpressionLhs const& );
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
     ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
 #  endif
 
 public:
     ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
     ExpressionLhs( ExpressionLhs const& ) = default;
     ExpressionLhs( ExpressionLhs && )     = default;
 #  endif
@@ -1307,11 +1395,11 @@ private:
 namespace Catch {
 
     template<typename T>
-    inline ExpressionLhs<T const&> ResultBuilder::operator->* ( T const& operand ) {
+    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
         return ExpressionLhs<T const&>( *this, operand );
     }
 
-    inline ExpressionLhs<bool> ResultBuilder::operator->* ( bool value ) {
+    inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) {
         return ExpressionLhs<bool>( *this, value );
     }
 
@@ -1401,6 +1489,8 @@ namespace Catch {
 
         virtual std::string getCurrentTestName() const = 0;
         virtual const AssertionResult* getLastResult() const = 0;
+
+        virtual void handleFatalErrorCondition( std::string const& message ) = 0;
     };
 
     IResultCapture& getResultCapture();
@@ -1481,7 +1571,7 @@ namespace Catch {
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
-            ( __catchResult->*expr ).endExpression(); \
+            ( __catchResult <= expr ).endExpression(); \
         } \
         catch( ... ) { \
             __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
@@ -1581,7 +1671,7 @@ namespace Catch {
             std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \
             __catchResult \
                 .setLhs( Catch::toString( arg ) ) \
-                .setRhs( matcherAsString == "{?}" ? #matcher : matcherAsString ) \
+                .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
                 .setOp( "matches" ) \
                 .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \
             __catchResult.captureExpression(); \
@@ -1642,6 +1732,9 @@ namespace Catch {
         bool allPassed() const {
             return failed == 0 && failedButOk == 0;
         }
+        bool allOk() const {
+            return failed == 0;
+        }
 
         std::size_t passed;
         std::size_t failed;
@@ -1694,7 +1787,7 @@ namespace Catch {
     public:
         Timer() : m_ticks( 0 ) {}
         void start();
-        unsigned int getElapsedNanoseconds() const;
+        unsigned int getElapsedMicroseconds() const;
         unsigned int getElapsedMilliseconds() const;
         double getElapsedSeconds() const;
 
@@ -1708,7 +1801,7 @@ namespace Catch {
 
 namespace Catch {
 
-    class Section {
+    class Section : NonCopyable {
     public:
         Section( SectionInfo const& info );
         ~Section();
@@ -1717,15 +1810,6 @@ namespace Catch {
         operator bool() const;
 
     private:
-#ifdef CATCH_CPP11_OR_GREATER
-        Section( Section const& )              = delete;
-        Section( Section && )                  = delete;
-        Section& operator = ( Section const& ) = delete;
-        Section& operator = ( Section && )     = delete;
-#else
-        Section( Section const& info );
-        Section& operator = ( Section const& );
-#endif
         SectionInfo m_info;
 
         std::string m_name;
@@ -2700,7 +2784,7 @@ return @ desc; \
 
 #endif
 
-#ifdef CATCH_CONFIG_RUNNER
+#ifdef CATCH_IMPL
 // #included from: internal/catch_impl.hpp
 #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
 
@@ -2712,7 +2796,7 @@ return @ desc; \
 #pragma clang diagnostic ignored "-Wweak-vtables"
 #endif
 
-// #included from: catch_runner.hpp
+// #included from: ../catch_runner.hpp
 #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
 
 // #included from: internal/catch_commandline.hpp
@@ -2968,6 +3052,11 @@ namespace Catch {
         Always,
         Never
     }; };
+    struct RunTests { enum InWhatOrder {
+        InDeclarationOrder,
+        InLexicographicalOrder,
+        InRandomOrder
+    }; };
 
     class TestSpec;
 
@@ -2985,6 +3074,9 @@ namespace Catch {
         virtual bool showInvisibles() const = 0;
         virtual ShowDurations::OrNot showDurations() const = 0;
         virtual TestSpec const& testSpec() const = 0;
+        virtual RunTests::InWhatOrder runOrder() const = 0;
+        virtual unsigned int rngSeed() const = 0;
+        virtual bool forceColour() const = 0;
     };
 }
 
@@ -3010,12 +3102,16 @@ namespace Catch {
     private:
         bool isOwned;
     };
+
+    std::ostream& cout();
+    std::ostream& cerr();
 }
 
 #include <memory>
 #include <vector>
 #include <string>
 #include <iostream>
+#include <ctime>
 
 #ifndef CATCH_CONFIG_CONSOLE_WIDTH
 #define CATCH_CONFIG_CONSOLE_WIDTH 80
@@ -3035,10 +3131,13 @@ namespace Catch {
             noThrow( false ),
             showHelp( false ),
             showInvisibles( false ),
+            forceColour( false ),
             abortAfter( -1 ),
+            rngSeed( 0 ),
             verbosity( Verbosity::Normal ),
             warnings( WarnAbout::Nothing ),
-            showDurations( ShowDurations::DefaultForReporter )
+            showDurations( ShowDurations::DefaultForReporter ),
+            runOrder( RunTests::InDeclarationOrder )
         {}
 
         bool listTests;
@@ -3051,12 +3150,15 @@ namespace Catch {
         bool noThrow;
         bool showHelp;
         bool showInvisibles;
+        bool forceColour;
 
         int abortAfter;
+        unsigned int rngSeed;
 
         Verbosity::Level verbosity;
         WarnAbout::What warnings;
         ShowDurations::OrNot showDurations;
+        RunTests::InWhatOrder runOrder;
 
         std::string reporterName;
         std::string outputFilename;
@@ -3074,12 +3176,12 @@ namespace Catch {
     public:
 
         Config()
-        :   m_os( std::cout.rdbuf() )
+        :   m_os( Catch::cout().rdbuf() )
         {}
 
         Config( ConfigData const& data )
         :   m_data( data ),
-            m_os( std::cout.rdbuf() )
+            m_os( Catch::cout().rdbuf() )
         {
             if( !data.testsOrTags.empty() ) {
                 TestSpecParser parser( ITagAliasRegistry::get() );
@@ -3090,7 +3192,7 @@ namespace Catch {
         }
 
         virtual ~Config() {
-            m_os.rdbuf( std::cout.rdbuf() );
+            m_os.rdbuf( Catch::cout().rdbuf() );
             m_stream.release();
         }
 
@@ -3112,7 +3214,7 @@ namespace Catch {
         bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
 
         void setStreamBuf( std::streambuf* buf ) {
-            m_os.rdbuf( buf ? buf : std::cout.rdbuf() );
+            m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() );
         }
 
         void useStream( std::string const& streamName ) {
@@ -3138,6 +3240,9 @@ namespace Catch {
         virtual bool includeSuccessfulResults() const   { return m_data.showSuccessfulTests; }
         virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
         virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
+        virtual RunTests::InWhatOrder runOrder() const  { return m_data.runOrder; }
+        virtual unsigned int rngSeed() const    { return m_data.rngSeed; }
+        virtual bool forceColour() const { return m_data.forceColour; }
 
     private:
         ConfigData m_data;
@@ -3399,7 +3504,7 @@ namespace Clara {
         template<typename ConfigT>
         struct IArgFunction {
             virtual ~IArgFunction() {}
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
             IArgFunction()                      = default;
             IArgFunction( IArgFunction const& ) = default;
 #  endif
@@ -3766,7 +3871,7 @@ namespace Clara {
             m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
         {
             if( other.m_floatingArg.get() )
-                m_floatingArg = ArgAutoPtr( new Arg( *other.m_floatingArg ) );
+                m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
         }
 
         CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
@@ -3794,7 +3899,7 @@ namespace Clara {
         ArgBuilder operator[]( UnpositionalTag ) {
             if( m_floatingArg.get() )
                 throw std::logic_error( "Only one unpositional argument can be added" );
-            m_floatingArg = ArgAutoPtr( new Arg() );
+            m_floatingArg.reset( new Arg() );
             ArgBuilder builder( m_floatingArg.get() );
             return builder;
         }
@@ -3936,7 +4041,7 @@ namespace Clara {
                 if( it == itEnd ) {
                     if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
                         unusedTokens.push_back( token );
-                    else if( m_throwOnUnrecognisedTokens )
+                    else if( errors.empty() && m_throwOnUnrecognisedTokens )
                         errors.push_back( "unrecognised option: " + token.data );
                 }
             }
@@ -4034,7 +4139,28 @@ namespace Catch {
             config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
         else
             throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
-
+    }
+    inline void setOrder( ConfigData& config, std::string const& order ) {
+        if( startsWith( "declared", order ) )
+            config.runOrder = RunTests::InDeclarationOrder;
+        else if( startsWith( "lexical", order ) )
+            config.runOrder = RunTests::InLexicographicalOrder;
+        else if( startsWith( "random", order ) )
+            config.runOrder = RunTests::InRandomOrder;
+        else
+            throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+    }
+    inline void setRngSeed( ConfigData& config, std::string const& seed ) {
+        if( seed == "time" ) {
+            config.rngSeed = static_cast<unsigned int>( std::time(0) );
+        }
+        else {
+            std::stringstream ss;
+            ss << seed;
+            ss >> config.rngSeed;
+            if( ss.fail() )
+                throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+        }
     }
     inline void setVerbosity( ConfigData& config, int level ) {
         // !TBD: accept strings?
@@ -4146,6 +4272,18 @@ namespace Catch {
             .describe( "list all reporters" )
             .bind( &ConfigData::listReporters );
 
+        cli["--order"]
+            .describe( "test case order (defaults to decl)" )
+            .bind( &setOrder, "decl|lex|rand" );
+
+        cli["--rng-seed"]
+            .describe( "set a specific seed for random numbers" )
+            .bind( &setRngSeed, "'time'|number" );
+
+        cli["--force-colour"]
+            .describe( "force colourised output" )
+            .bind( &ConfigData::forceColour );
+
         return cli;
     }
 
@@ -4319,10 +4457,6 @@ namespace Catch {
 
 namespace Catch {
 
-    namespace Detail {
-        struct IColourImpl;
-    }
-
     struct Colour {
         enum Code {
             None = 0,
@@ -4368,7 +4502,6 @@ namespace Catch {
         static void use( Code _colourCode );
 
     private:
-        static Detail::IColourImpl* impl();
         bool m_moved;
     };
 
@@ -4462,7 +4595,7 @@ namespace Catch
         }
         virtual ~AssertionStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         AssertionStats( AssertionStats const& )              = default;
         AssertionStats( AssertionStats && )                  = default;
         AssertionStats& operator = ( AssertionStats const& ) = default;
@@ -4485,7 +4618,7 @@ namespace Catch
             missingAssertions( _missingAssertions )
         {}
         virtual ~SectionStats();
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         SectionStats( SectionStats const& )              = default;
         SectionStats( SectionStats && )                  = default;
         SectionStats& operator = ( SectionStats const& ) = default;
@@ -4512,7 +4645,7 @@ namespace Catch
         {}
         virtual ~TestCaseStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestCaseStats( TestCaseStats const& )              = default;
         TestCaseStats( TestCaseStats && )                  = default;
         TestCaseStats& operator = ( TestCaseStats const& ) = default;
@@ -4540,7 +4673,7 @@ namespace Catch
         {}
         virtual ~TestGroupStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestGroupStats( TestGroupStats const& )              = default;
         TestGroupStats( TestGroupStats && )                  = default;
         TestGroupStats& operator = ( TestGroupStats const& ) = default;
@@ -4562,7 +4695,7 @@ namespace Catch
         {}
         virtual ~TestRunStats();
 
-#  ifndef CATCH_CPP11_OR_GREATER
+#  ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestRunStats( TestRunStats const& _other )
         :   runInfo( _other.runInfo ),
             totals( _other.totals ),
@@ -4598,11 +4731,14 @@ namespace Catch
 
         virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
 
+        // The return value indicates if the messages buffer should be cleared:
         virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
         virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
         virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
         virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
         virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
     };
 
     struct IReporterFactory {
@@ -4630,9 +4766,9 @@ namespace Catch {
 
         TestSpec testSpec = config.testSpec();
         if( config.testSpec().hasFilters() )
-            std::cout << "Matching test cases:\n";
+            Catch::cout() << "Matching test cases:\n";
         else {
-            std::cout << "All available test cases:\n";
+            Catch::cout() << "All available test cases:\n";
             testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
         }
 
@@ -4653,15 +4789,15 @@ namespace Catch {
                 : Colour::None;
             Colour colourGuard( colour );
 
-            std::cout << Text( testCaseInfo.name, nameAttr ) << std::endl;
+            Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
             if( !testCaseInfo.tags.empty() )
-                std::cout << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
+                Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
         }
 
         if( !config.testSpec().hasFilters() )
-            std::cout << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
         else
-            std::cout << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
         return matchedTests;
     }
 
@@ -4677,7 +4813,7 @@ namespace Catch {
                 ++it ) {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
-            std::cout << testCaseInfo.name << std::endl;
+            Catch::cout() << testCaseInfo.name << std::endl;
         }
         return matchedTests;
     }
@@ -4703,9 +4839,9 @@ namespace Catch {
     inline std::size_t listTags( Config const& config ) {
         TestSpec testSpec = config.testSpec();
         if( config.testSpec().hasFilters() )
-            std::cout << "Tags for matching test cases:\n";
+            Catch::cout() << "Tags for matching test cases:\n";
         else {
-            std::cout << "All available tags:\n";
+            Catch::cout() << "All available tags:\n";
             testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
         }
 
@@ -4739,14 +4875,14 @@ namespace Catch {
                                                     .setInitialIndent( 0 )
                                                     .setIndent( oss.str().size() )
                                                     .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
-            std::cout << oss.str() << wrapper << "\n";
+            Catch::cout() << oss.str() << wrapper << "\n";
         }
-        std::cout << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
         return tagCounts.size();
     }
 
     inline std::size_t listReporters( Config const& /*config*/ ) {
-        std::cout << "Available reports:\n";
+        Catch::cout() << "Available reporters:\n";
         IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
         IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
         std::size_t maxNameLen = 0;
@@ -4758,13 +4894,13 @@ namespace Catch {
                                                         .setInitialIndent( 0 )
                                                         .setIndent( 7+maxNameLen )
                                                         .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
-            std::cout << "  "
+            Catch::cout() << "  "
                     << it->first
                     << ":"
                     << std::string( maxNameLen - it->first.size() + 2, ' ' )
                     << wrapper << "\n";
         }
-        std::cout << std::endl;
+        Catch::cout() << std::endl;
         return factories.size();
     }
 
@@ -4814,32 +4950,15 @@ namespace SectionTracking {
 
         RunState runState() const { return m_runState; }
 
-        TrackedSection* findChild( std::string const& childName ) {
-            TrackedSections::iterator it = m_children.find( childName );
-            return it != m_children.end()
-                ? &it->second
-                : NULL;
-        }
-        TrackedSection* acquireChild( std::string const& childName ) {
-            if( TrackedSection* child = findChild( childName ) )
-                return child;
-            m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) );
-            return findChild( childName );
-        }
+        TrackedSection* findChild( std::string const& childName );
+        TrackedSection* acquireChild( std::string const& childName );
+
         void enter() {
             if( m_runState == NotStarted )
                 m_runState = Executing;
         }
-        void leave() {
-            for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end();
-                    it != itEnd;
-                    ++it )
-                if( it->second.runState() != Completed ) {
-                    m_runState = ExecutingChildren;
-                    return;
-                }
-            m_runState = Completed;
-        }
+        void leave();
+
         TrackedSection* getParent() {
             return m_parent;
         }
@@ -4852,9 +4971,31 @@ namespace SectionTracking {
         RunState m_runState;
         TrackedSections m_children;
         TrackedSection* m_parent;
-
     };
 
+    inline TrackedSection* TrackedSection::findChild( std::string const& childName ) {
+        TrackedSections::iterator it = m_children.find( childName );
+        return it != m_children.end()
+            ? &it->second
+            : NULL;
+    }
+    inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) {
+        if( TrackedSection* child = findChild( childName ) )
+            return child;
+        m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) );
+        return findChild( childName );
+    }
+    inline void TrackedSection::leave() {
+        for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end();
+                it != itEnd;
+                ++it )
+            if( it->second.runState() != Completed ) {
+                m_runState = ExecutingChildren;
+                return;
+            }
+        m_runState = Completed;
+    }
+
     class TestCaseTracker {
     public:
         TestCaseTracker( std::string const& testCaseName )
@@ -4921,6 +5062,81 @@ using SectionTracking::TestCaseTracker;
 
 } // namespace Catch
 
+// #included from: catch_fatal_condition.hpp
+#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
+
+namespace Catch {
+
+    // Report the error condition then exit the process
+    inline void fatal( std::string const& message, int exitCode ) {
+        IContext& context = Catch::getCurrentContext();
+        IResultCapture* resultCapture = context.getResultCapture();
+        resultCapture->handleFatalErrorCondition( message );
+
+		if( Catch::alwaysTrue() ) // avoids "no return" warnings
+            exit( exitCode );
+    }
+
+} // namespace Catch
+
+#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+		void reset() {}
+	};
+
+} // namespace Catch
+
+#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+
+#include <signal.h>
+
+namespace Catch {
+
+    struct SignalDefs { int id; const char* name; };
+    extern SignalDefs signalDefs[];
+    SignalDefs signalDefs[] = {
+            { SIGINT,  "SIGINT - Terminal interrupt signal" },
+            { SIGILL,  "SIGILL - Illegal instruction signal" },
+            { SIGFPE,  "SIGFPE - Floating point error signal" },
+            { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+            { SIGTERM, "SIGTERM - Termination request signal" },
+            { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+        };
+
+    struct FatalConditionHandler {
+
+        static void handleSignal( int sig ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                if( sig == signalDefs[i].id )
+                    fatal( signalDefs[i].name, -sig );
+            fatal( "<unknown signal>", -sig );
+        }
+
+        FatalConditionHandler() : m_isSet( true ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                signal( signalDefs[i].id, handleSignal );
+        }
+        ~FatalConditionHandler() {
+            reset();
+        }
+        void reset() {
+            if( m_isSet ) {
+                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                    signal( signalDefs[i].id, SIG_DFL );
+                m_isSet = false;
+            }
+        }
+
+        bool m_isSet;
+    };
+
+} // namespace Catch
+
+#endif // not Windows
+
 #include <set>
 #include <string>
 
@@ -5076,7 +5292,7 @@ namespace Catch {
         }
 
         virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) {
-            /*if( std::uncaught_exception() ) { // XXX Hack that makes Catch not run in loop in certain situations
+            /* if( std::uncaught_exception() ) { // XXX Hack that makes Catch not run in loop in certain situations
                 m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) );
                 return;
             }*/
@@ -5108,6 +5324,37 @@ namespace Catch {
             return &m_lastResult;
         }
 
+        virtual void handleFatalErrorCondition( std::string const& message ) {
+            ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
+            resultBuilder.setResultType( ResultWas::FatalErrorCondition );
+            resultBuilder << message;
+            resultBuilder.captureExpression();
+
+            handleUnfinishedSections();
+
+            // Recreate section for test case (as we will lose the one that was in scope)
+            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+
+            Counts assertions;
+            assertions.failed = 1;
+            SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
+            m_reporter->sectionEnded( testCaseSectionStats );
+
+            TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
+
+            Totals deltaTotals;
+            deltaTotals.testCases.failed = 1;
+            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
+                                                        deltaTotals,
+                                                        "",
+                                                        "",
+                                                        false ) );
+            m_totals.testCases.failed++;
+            testGroupEnded( "", m_totals, 1, 1 );
+            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
+        }
+
     public:
         // !TBD We need to do this another way!
         bool aborting() const {
@@ -5129,12 +5376,12 @@ namespace Catch {
                 Timer timer;
                 timer.start();
                 if( m_reporter->getPreferences().shouldRedirectStdOut ) {
-                    StreamRedirect coutRedir( std::cout, redirectedCout );
-                    StreamRedirect cerrRedir( std::cerr, redirectedCerr );
-                    m_activeTestCase->invoke();
+                    StreamRedirect coutRedir( Catch::cout(), redirectedCout );
+                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+                    invokeActiveTestCase();
                 }
                 else {
-                    m_activeTestCase->invoke();
+                    invokeActiveTestCase();
                 }
                 duration = timer.getElapsedSeconds();
             }
@@ -5142,20 +5389,9 @@ namespace Catch {
                 // This just means the test was aborted due to failure
             }
             catch(...) {
-                ResultBuilder exResult( m_lastAssertionInfo.macroName.c_str(),
-                                        m_lastAssertionInfo.lineInfo,
-                                        m_lastAssertionInfo.capturedExpression.c_str(),
-                                        m_lastAssertionInfo.resultDisposition );
-                exResult.useActiveException();
+                makeUnexpectedResultBuilder().useActiveException();
             }
-            // If sections ended prematurely due to an exception we stored their
-            // infos here so we can tear them down outside the unwind process.
-            for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
-                        itEnd = m_unfinishedSections.rend();
-                    it != itEnd;
-                    ++it )
-                sectionEnded( it->info, it->prevAssertions, it->durationInSeconds );
-            m_unfinishedSections.clear();
+            handleUnfinishedSections();
             m_messages.clear();
 
             Counts assertions = m_totals.assertions - prevAssertions;
@@ -5171,7 +5407,32 @@ namespace Catch {
             m_reporter->sectionEnded( testCaseSectionStats );
         }
 
+        void invokeActiveTestCase() {
+            FatalConditionHandler fatalConditionHandler; // Handle signals
+            m_activeTestCase->invoke();
+            fatalConditionHandler.reset();
+        }
+
     private:
+
+        ResultBuilder makeUnexpectedResultBuilder() const {
+            return ResultBuilder(   m_lastAssertionInfo.macroName.c_str(),
+                                    m_lastAssertionInfo.lineInfo,
+                                    m_lastAssertionInfo.capturedExpression.c_str(),
+                                    m_lastAssertionInfo.resultDisposition );
+        }
+
+        void handleUnfinishedSections() {
+            // If sections ended prematurely due to an exception we stored their
+            // infos here so we can tear them down outside the unwind process.
+            for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
+                        itEnd = m_unfinishedSections.rend();
+                    it != itEnd;
+                    ++it )
+                sectionEnded( it->info, it->prevAssertions, it->durationInSeconds );
+            m_unfinishedSections.clear();
+        }
+
         struct UnfinishedSections {
             UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds )
             : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
@@ -5217,18 +5478,19 @@ namespace Catch {
     struct Version {
         Version(    unsigned int _majorVersion,
                     unsigned int _minorVersion,
-                    unsigned int _buildNumber,
-                    char const* const _branchName )
-        :   majorVersion( _majorVersion ),
-            minorVersion( _minorVersion ),
-            buildNumber( _buildNumber ),
-            branchName( _branchName )
-        {}
+                    unsigned int _patchNumber,
+                    std::string const& _branchName,
+                    unsigned int _buildNumber );
 
         unsigned int const majorVersion;
         unsigned int const minorVersion;
+        unsigned int const patchNumber;
+
+        // buildNumber is only used if branchName is not null
+        std::string const branchName;
         unsigned int const buildNumber;
-        char const* const branchName;
+
+        friend std::ostream& operator << ( std::ostream& os, Version const& version );
 
     private:
         void operator=( Version const& );
@@ -5259,7 +5521,7 @@ namespace Catch {
 
             Totals totals;
 
-            context.testGroupStarting( "", 1, 1 ); // deprecated?
+            context.testGroupStarting( "all tests", 1, 1 ); // deprecated?
 
             TestSpec testSpec = m_config->testSpec();
             if( !testSpec.hasFilters() )
@@ -5282,7 +5544,15 @@ namespace Catch {
                     m_testsAlreadyRun.insert( *it );
                 }
             }
-            context.testGroupEnded( "", totals, 1, 1 );
+            std::vector<TestCase> skippedTestCases;
+            getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true );
+
+            for( std::vector<TestCase>::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end();
+                    it != itEnd;
+                    ++it )
+                m_reporter->skipTest( *it );
+
+            context.testGroupEnded( "all tests", totals, 1, 1 );
             return totals;
         }
 
@@ -5319,7 +5589,7 @@ namespace Catch {
         std::set<TestCase> m_testsAlreadyRun;
     };
 
-    class Session {
+    class Session : NonCopyable {
         static bool alreadyInstantiated;
 
     public:
@@ -5330,7 +5600,7 @@ namespace Catch {
         : m_cli( makeCommandLineParser() ) {
             if( alreadyInstantiated ) {
                 std::string msg = "Only one instance of Catch::Session can ever be used";
-                std::cerr << msg << std::endl;
+                Catch::cerr() << msg << std::endl;
                 throw std::logic_error( msg );
             }
             alreadyInstantiated = true;
@@ -5340,15 +5610,10 @@ namespace Catch {
         }
 
         void showHelp( std::string const& processName ) {
-            std::cout << "\nCatch v"    << libraryVersion.majorVersion << "."
-                                        << libraryVersion.minorVersion << " build "
-                                        << libraryVersion.buildNumber;
-            if( libraryVersion.branchName != std::string( "master" ) )
-                std::cout << " (" << libraryVersion.branchName << " branch)";
-            std::cout << "\n";
+            Catch::cout() << "\nCatch v" << libraryVersion << "\n";
 
-            m_cli.usage( std::cout, processName );
-            std::cout << "For more detail usage please see the project docs\n" << std::endl;
+            m_cli.usage( Catch::cout(), processName );
+            Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
         }
 
         int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
@@ -5362,11 +5627,12 @@ namespace Catch {
             catch( std::exception& ex ) {
                 {
                     Colour colourGuard( Colour::Red );
-                    std::cerr   << "\nError(s) in input:\n"
-                                << Text( ex.what(), TextAttributes().setIndent(2) )
-                                << "\n\n";
+                    Catch::cerr()
+                        << "\nError(s) in input:\n"
+                        << Text( ex.what(), TextAttributes().setIndent(2) )
+                        << "\n\n";
                 }
-                m_cli.usage( std::cout, m_configData.processName );
+                m_cli.usage( Catch::cout(), m_configData.processName );
                 return (std::numeric_limits<int>::max)();
             }
             return 0;
@@ -5392,6 +5658,9 @@ namespace Catch {
             try
             {
                 config(); // Force config to be constructed
+
+                std::srand( m_configData.rngSeed );
+
                 Runner runner( m_config );
 
                 // Handle list request
@@ -5401,7 +5670,7 @@ namespace Catch {
                 return static_cast<int>( runner.runTests().assertions.failed );
             }
             catch( std::exception& ex ) {
-                std::cerr << ex.what() << std::endl;
+                Catch::cerr() << ex.what() << std::endl;
                 return (std::numeric_limits<int>::max)();
             }
         }
@@ -5442,10 +5711,18 @@ namespace Catch {
 #include <set>
 #include <sstream>
 #include <iostream>
+#include <algorithm>
 
 namespace Catch {
 
     class TestRegistry : public ITestCaseRegistry {
+        struct LexSort {
+            bool operator() (TestCase i,TestCase j) const { return (i<j);}
+        };
+        struct RandomNumberGenerator {
+            int operator()( int n ) const { return std::rand() % n; }
+        };
+
     public:
         TestRegistry() : m_unnamedCount( 0 ) {}
         virtual ~TestRegistry();
@@ -5468,7 +5745,7 @@ namespace Catch {
                 TestCase const& prev = *m_functions.find( testCase );
                 {
                     Colour colourGuard( Colour::Red );
-                    std::cerr   << "error: TEST_CASE( \"" << name << "\" ) already defined.\n"
+                    Catch::cerr()   << "error: TEST_CASE( \"" << name << "\" ) already defined.\n"
                                 << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n"
                                 << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl;
                 }
@@ -5484,18 +5761,38 @@ namespace Catch {
             return m_nonHiddenFunctions;
         }
 
-        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases ) const {
+        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const {
+
             for( std::vector<TestCase>::const_iterator  it = m_functionsInOrder.begin(),
                                                         itEnd = m_functionsInOrder.end();
                     it != itEnd;
                     ++it ) {
-                if( testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ) )
+                bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() );
+                if( includeTest != negated )
                     matchingTestCases.push_back( *it );
             }
+            sortTests( config, matchingTestCases );
         }
 
     private:
 
+        static void sortTests( IConfig const& config, std::vector<TestCase>& matchingTestCases ) {
+
+            switch( config.runOrder() ) {
+                case RunTests::InLexicographicalOrder:
+                    std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() );
+                    break;
+                case RunTests::InRandomOrder:
+                {
+                    RandomNumberGenerator rng;
+                    std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng );
+                }
+                    break;
+                case RunTests::InDeclarationOrder:
+                    // already in declaration order
+                    break;
+            }
+        }
         std::set<TestCase> m_functions;
         std::vector<TestCase> m_functionsInOrder;
         std::vector<TestCase> m_nonHiddenFunctions;
@@ -5619,7 +5916,7 @@ namespace Catch {
                     throw;
                 }
                 @catch (NSException *exception) {
-                    return toString( [exception description] );
+                    return Catch::toString( [exception description] );
                 }
 #else
                 throw;
@@ -5766,6 +6063,7 @@ namespace Catch {
 
 #include <stdexcept>
 #include <cstdio>
+#include <iostream>
 
 namespace Catch {
 
@@ -5829,6 +6127,15 @@ namespace Catch {
             isOwned = false;
         }
     }
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions
+    std::ostream& cout() {
+        return std::cout;
+    }
+    std::ostream& cerr() {
+        return std::cerr;
+    }
+#endif
 }
 
 namespace Catch {
@@ -5878,7 +6185,7 @@ namespace Catch {
             std::string testName = getResultCapture()->getCurrentTestName();
 
             std::map<std::string, IGeneratorsForTest*>::const_iterator it =
-            m_generatorsByTestName.find( testName );
+                m_generatorsByTestName.find( testName );
             return it != m_generatorsByTestName.end()
                 ? it->second
                 : NULL;
@@ -5914,8 +6221,8 @@ namespace Catch {
     }
 
     Stream createStream( std::string const& streamName ) {
-        if( streamName == "stdout" ) return Stream( std::cout.rdbuf(), false );
-        if( streamName == "stderr" ) return Stream( std::cerr.rdbuf(), false );
+        if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false );
+        if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false );
         if( streamName == "debug" ) return Stream( new StreamBufImpl<OutputDebugWriter>, true );
 
         throw std::domain_error( "Unknown stream: " + streamName );
@@ -5930,14 +6237,35 @@ namespace Catch {
 // #included from: catch_console_colour_impl.hpp
 #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
 
-namespace Catch { namespace Detail {
-    struct IColourImpl {
-        virtual ~IColourImpl() {}
-        virtual void use( Colour::Code _colourCode ) = 0;
-    };
-}}
+namespace Catch {
+    namespace {
 
-#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+        struct IColourImpl {
+            virtual ~IColourImpl() {}
+            virtual void use( Colour::Code _colourCode ) = 0;
+        };
+
+        struct NoColourImpl : IColourImpl {
+            void use( Colour::Code ) {}
+
+            static IColourImpl* instance() {
+                static NoColourImpl s_instance;
+                return &s_instance;
+            }
+        };
+
+    } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+#   ifdef CATCH_PLATFORM_WINDOWS
+#       define CATCH_CONFIG_COLOUR_WINDOWS
+#   else
+#       define CATCH_CONFIG_COLOUR_ANSI
+#   endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
 
 #ifndef NOMINMAX
 #define NOMINMAX
@@ -5952,7 +6280,7 @@ namespace Catch { namespace Detail {
 namespace Catch {
 namespace {
 
-    class Win32ColourImpl : public Detail::IColourImpl {
+    class Win32ColourImpl : public IColourImpl {
     public:
         Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
         {
@@ -5989,11 +6317,7 @@ namespace {
         WORD originalAttributes;
     };
 
-    inline bool shouldUseColourForPlatform() {
-        return true;
-    }
-
-    static Detail::IColourImpl* platformColourInstance() {
+    IColourImpl* platformColourInstance() {
         static Win32ColourImpl s_instance;
         return &s_instance;
     }
@@ -6001,7 +6325,7 @@ namespace {
 } // end anon namespace
 } // end namespace Catch
 
-#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
 
 #include <unistd.h>
 
@@ -6012,7 +6336,7 @@ namespace {
     // Thanks to Adam Strzelecki for original contribution
     // (http://github.com/nanoant)
     // https://github.com/philsquared/Catch/pull/131
-    class PosixColourImpl : public Detail::IColourImpl {
+    class PosixColourImpl : public IColourImpl {
     public:
         virtual void use( Colour::Code _colourCode ) {
             switch( _colourCode ) {
@@ -6033,53 +6357,48 @@ namespace {
                 case Colour::Bright: throw std::logic_error( "not a colour" );
             }
         }
+        static IColourImpl* instance() {
+            static PosixColourImpl s_instance;
+            return &s_instance;
+        }
+
     private:
         void setColour( const char* _escapeCode ) {
-            std::cout << '\033' << _escapeCode;
+            Catch::cout() << '\033' << _escapeCode;
         }
     };
 
-    inline bool shouldUseColourForPlatform() {
-        return isatty(STDOUT_FILENO);
-    }
-
-    static Detail::IColourImpl* platformColourInstance() {
-        static PosixColourImpl s_instance;
-        return &s_instance;
+    IColourImpl* platformColourInstance() {
+        Ptr<IConfig const> config = getCurrentContext().getConfig();
+        return (config && config->forceColour()) || isatty(STDOUT_FILENO)
+            ? PosixColourImpl::instance()
+            : NoColourImpl::instance();
     }
 
 } // end anon namespace
 } // end namespace Catch
 
-#endif // not Windows
+#else  // not Windows or ANSI ///////////////////////////////////////////////
 
 namespace Catch {
 
-    namespace {
-        struct NoColourImpl : Detail::IColourImpl {
-            void use( Colour::Code ) {}
+    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
 
-            static IColourImpl* instance() {
-                static NoColourImpl s_instance;
-                return &s_instance;
-            }
-        };
-        static bool shouldUseColour() {
-            return shouldUseColourForPlatform() && !isDebuggerActive();
-        }
-    }
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
 
     Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
     Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
     Colour::~Colour(){ if( !m_moved ) use( None ); }
-    void Colour::use( Code _colourCode ) {
-        impl()->use( _colourCode );
-    }
 
-    Detail::IColourImpl* Colour::impl() {
-        return shouldUseColour()
-            ? platformColourInstance()
-            : NoColourImpl::instance();
+    void Colour::use( Code _colourCode ) {
+        static IColourImpl* impl = isDebuggerActive()
+            ? NoColourImpl::instance()
+            : platformColourInstance();
+        impl->use( _colourCode );
     }
 
 } // end namespace Catch
@@ -6244,7 +6563,7 @@ namespace Catch {
 namespace Catch {
 
     inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
-        if( tag == "." ||
+        if( startsWith( tag, "." ) ||
             tag == "hide" ||
             tag == "!hide" )
             return TestCaseInfo::IsHidden;
@@ -6264,13 +6583,13 @@ namespace Catch {
         if( isReservedTag( tag ) ) {
             {
                 Colour colourGuard( Colour::Red );
-                std::cerr
+                Catch::cerr()
                     << "Tag name [" << tag << "] not allowed.\n"
                     << "Tag names starting with non alpha-numeric characters are reserved\n";
             }
             {
                 Colour colourGuard( Colour::FileName );
-                std::cerr << _lineInfo << std::endl;
+                Catch::cerr() << _lineInfo << std::endl;
             }
             exit(1);
         }
@@ -6298,14 +6617,15 @@ namespace Catch {
             }
             else {
                 if( c == ']' ) {
-                    enforceNotReservedTag( tag, _lineInfo );
-
-                    inTag = false;
-                    if( tag == "hide" || tag == "." )
+                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+                    if( prop == TestCaseInfo::IsHidden )
                         isHidden = true;
-                    else
-                        tags.insert( tag );
+                    else if( prop == TestCaseInfo::None )
+                        enforceNotReservedTag( tag, _lineInfo );
+
+                    tags.insert( tag );
                     tag.clear();
+                    inTag = false;
                 }
                 else
                     tag += c;
@@ -6422,8 +6742,33 @@ namespace Catch {
 
 namespace Catch {
 
-    // These numbers are maintained by a script
-    Version libraryVersion( 1, 0, 53, "master" );
+    Version::Version
+        (   unsigned int _majorVersion,
+            unsigned int _minorVersion,
+            unsigned int _patchNumber,
+            std::string const& _branchName,
+            unsigned int _buildNumber )
+    :   majorVersion( _majorVersion ),
+        minorVersion( _minorVersion ),
+        patchNumber( _patchNumber ),
+        branchName( _branchName ),
+        buildNumber( _buildNumber )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, Version const& version ) {
+        os  << version.majorVersion << "."
+            << version.minorVersion << "."
+            << version.patchNumber;
+
+        if( !version.branchName.empty() ) {
+            os  << "-" << version.branchName
+                << "." << version.buildNumber;
+        }
+        return os;
+    }
+
+    Version libraryVersion( 1, 2, 1, "", 0 );
+
 }
 
 // #included from: catch_message.hpp
@@ -6507,6 +6852,7 @@ namespace Catch
         virtual void testCaseEnded( TestCaseStats const& testCaseStats );
         virtual void testGroupEnded( TestGroupStats const& testGroupStats );
         virtual void testRunEnded( TestRunStats const& testRunStats );
+        virtual void skipTest( TestCaseInfo const& );
 
     private:
         Ptr<IReporter> m_legacyReporter;
@@ -6580,6 +6926,8 @@ namespace Catch
     void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
         m_legacyReporter->EndTesting( testRunStats.totals );
     }
+    void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
+    }
 }
 
 // #included from: catch_timer.hpp
@@ -6602,11 +6950,11 @@ namespace Catch {
         uint64_t getCurrentTicks() {
             static uint64_t hz=0, hzo=0;
             if (!hz) {
-                QueryPerformanceFrequency((LARGE_INTEGER*)&hz);
-                QueryPerformanceCounter((LARGE_INTEGER*)&hzo);
+                QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
+                QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
             }
             uint64_t t;
-            QueryPerformanceCounter((LARGE_INTEGER*)&t);
+            QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
             return ((t-hzo)*1000000)/hz;
         }
 #else
@@ -6621,14 +6969,14 @@ namespace Catch {
     void Timer::start() {
         m_ticks = getCurrentTicks();
     }
-    unsigned int Timer::getElapsedNanoseconds() const {
+    unsigned int Timer::getElapsedMicroseconds() const {
         return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
     }
     unsigned int Timer::getElapsedMilliseconds() const {
-        return static_cast<unsigned int>((getCurrentTicks() - m_ticks)/1000);
+        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
     }
     double Timer::getElapsedSeconds() const {
-        return (getCurrentTicks() - m_ticks)/1000000.0;
+        return getElapsedMicroseconds()/1000000.0;
     }
 
 } // namespace Catch
@@ -6666,6 +7014,20 @@ namespace Catch {
         return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
     }
 
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+        bool replaced = false;
+        std::size_t i = str.find( replaceThis );
+        while( i != std::string::npos ) {
+            replaced = true;
+            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+            if( i < str.size()-withThis.size() )
+                i = str.find( replaceThis, i+withThis.size() );
+            else
+                i = std::string::npos;
+        }
+        return replaced;
+    }
+
     pluralise::pluralise( std::size_t count, std::string const& label )
     :   m_count( count ),
         m_label( label )
@@ -6693,6 +7055,9 @@ namespace Catch {
     bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
         return line == other.line && file == other.file;
     }
+    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
+        return line < other.line || ( line == other.line  && file < other.file );
+    }
 
     std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
 #ifndef __GNUG__
@@ -6787,7 +7152,7 @@ namespace Catch {
 
             size = sizeof(info);
             if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) {
-                std::cerr << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
                 return false;
             }
 
@@ -6828,7 +7193,7 @@ namespace Catch {
     namespace Catch {
         void writeToDebugConsole( std::string const& text ) {
             // !TBD: Need a version for Mac/ XCode and other IDEs
-            std::cout << text;
+            Catch::cout() << text;
         }
     }
 #endif // Platform
@@ -6840,6 +7205,8 @@ namespace Catch {
 
 namespace Detail {
 
+    std::string unprintableString = "{?}";
+
     namespace {
         struct Endianness {
             enum Arch { Big, Little };
@@ -6898,7 +7265,7 @@ std::string toString( std::wstring const& value ) {
     s.reserve( value.size() );
     for(size_t i = 0; i < value.size(); ++i )
         s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
-    return toString( s );
+    return Catch::toString( s );
 }
 
 std::string toString( const char* const value ) {
@@ -6922,20 +7289,21 @@ std::string toString( wchar_t* const value )
 std::string toString( int value ) {
     std::ostringstream oss;
     oss << value;
+    if( value >= 255 )
+        oss << " (0x" << std::hex << value << ")";
     return oss.str();
 }
 
 std::string toString( unsigned long value ) {
     std::ostringstream oss;
-    if( value > 8192 )
-        oss << "0x" << std::hex << value;
-    else
-        oss << value;
+    oss << value;
+    if( value >= 255 )
+        oss << " (0x" << std::hex << value << ")";
     return oss.str();
 }
 
 std::string toString( unsigned int value ) {
-    return toString( static_cast<unsigned long>( value ) );
+    return Catch::toString( static_cast<unsigned long>( value ) );
 }
 
 template<typename T>
@@ -7061,7 +7429,7 @@ namespace Catch {
         if( !result.isOk() ) {
             if( getCurrentContext().getConfig()->shouldDebugBreak() )
                 m_shouldDebugBreak = true;
-            if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal )
+            if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
                 m_shouldThrow = true;
         }
     }
@@ -7201,7 +7569,7 @@ namespace Catch {
         }
         catch( std::exception& ex ) {
             Colour colourGuard( Colour::Red );
-            std::cerr << ex.what() << std::endl;
+            Catch::cerr() << ex.what() << std::endl;
             exit(1);
         }
     }
@@ -7214,6 +7582,8 @@ namespace Catch {
 // #included from: catch_reporter_bases.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 
+#include <cstring>
+
 namespace Catch {
 
     struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
@@ -7246,7 +7616,6 @@ namespace Catch {
         }
         virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) {
             currentTestCaseInfo.reset();
-            assert( m_sectionStack.empty() );
         }
         virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) {
             currentGroupInfo.reset();
@@ -7257,6 +7626,11 @@ namespace Catch {
             currentTestRunInfo.reset();
         }
 
+        virtual void skipTest( TestCaseInfo const& ) {
+            // Don't do anything with this by default.
+            // It can optionally be overridden in the derived class.
+        }
+
         Ptr<IConfig> m_config;
         std::ostream& stream;
 
@@ -7386,6 +7760,8 @@ namespace Catch {
         }
         virtual void testRunEndedCumulative() = 0;
 
+        virtual void skipTest( TestCaseInfo const& ) {}
+
         Ptr<IConfig> m_config;
         std::ostream& stream;
         std::vector<AssertionStats> m_assertions;
@@ -7401,6 +7777,16 @@ namespace Catch {
 
     };
 
+    template<char C>
+    char const* getLineOfChars() {
+        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+        if( !*line ) {
+            memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+        }
+        return line;
+    }
+
 } // end namespace Catch
 
 // #included from: ../internal/catch_reporter_registrars.hpp
@@ -7470,7 +7856,6 @@ namespace Catch {
 #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 
 #include <sstream>
-#include <iostream>
 #include <string>
 #include <vector>
 
@@ -7513,7 +7898,7 @@ namespace Catch {
         XmlWriter()
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
-            m_os( &std::cout )
+            m_os( &Catch::cout() )
         {}
 
         XmlWriter( std::ostream& os )
@@ -7527,27 +7912,6 @@ namespace Catch {
                 endElement();
         }
 
-//#  ifndef CATCH_CPP11_OR_GREATER
-//        XmlWriter& operator = ( XmlWriter const& other ) {
-//            XmlWriter temp( other );
-//            swap( temp );
-//            return *this;
-//        }
-//#  else
-//        XmlWriter( XmlWriter const& )              = default;
-//        XmlWriter( XmlWriter && )                  = default;
-//        XmlWriter& operator = ( XmlWriter const& ) = default;
-//        XmlWriter& operator = ( XmlWriter && )     = default;
-//#  endif
-//
-//        void swap( XmlWriter& other ) {
-//            std::swap( m_tagIsOpen, other.m_tagIsOpen );
-//            std::swap( m_needsNewline, other.m_needsNewline );
-//            std::swap( m_tags, other.m_tags );
-//            std::swap( m_indent, other.m_indent );
-//            std::swap( m_os, other.m_os );
-//        }
-
         XmlWriter& startElement( std::string const& name ) {
             ensureTagClosed();
             newlineIfNecessary();
@@ -7683,81 +8047,90 @@ namespace Catch {
 
 }
 namespace Catch {
-    class XmlReporter : public SharedImpl<IReporter> {
+    class XmlReporter : public StreamingReporterBase {
     public:
-        XmlReporter( ReporterConfig const& config ) : m_config( config ), m_sectionDepth( 0 ) {}
+        XmlReporter( ReporterConfig const& _config )
+        :   StreamingReporterBase( _config ),
+            m_sectionDepth( 0 )
+        {}
+
+        virtual ~XmlReporter();
 
         static std::string getDescription() {
             return "Reports test results as an XML document";
         }
-        virtual ~XmlReporter();
 
-    private: // IReporter
-
-        virtual bool shouldRedirectStdout() const {
-            return true;
+    public: // StreamingReporterBase
+        virtual ReporterPreferences getPreferences() const {
+            ReporterPreferences prefs;
+            prefs.shouldRedirectStdOut = true;
+            return prefs;
         }
 
-        virtual void StartTesting() {
-            m_xml.setStream( m_config.stream() );
-            m_xml.startElement( "Catch" );
-            if( !m_config.fullConfig()->name().empty() )
-                m_xml.writeAttribute( "name", m_config.fullConfig()->name() );
+        virtual void noMatchingTestCases( std::string const& s ) {
+            StreamingReporterBase::noMatchingTestCases( s );
         }
 
-        virtual void EndTesting( const Totals& totals ) {
-            m_xml.scopedElement( "OverallResults" )
-                .writeAttribute( "successes", totals.assertions.passed )
-                .writeAttribute( "failures", totals.assertions.failed )
-                .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
-            m_xml.endElement();
+        virtual void testRunStarting( TestRunInfo const& testInfo ) {
+            StreamingReporterBase::testRunStarting( testInfo );
+            m_xml.setStream( stream );
+            m_xml.startElement( "Catch" );
+            if( !m_config->name().empty() )
+                m_xml.writeAttribute( "name", m_config->name() );
         }
 
-        virtual void StartGroup( const std::string& groupName ) {
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) {
+            StreamingReporterBase::testGroupStarting( groupInfo );
             m_xml.startElement( "Group" )
-                .writeAttribute( "name", groupName );
+                .writeAttribute( "name", groupInfo.name );
         }
 
-        virtual void EndGroup( const std::string&, const Totals& totals ) {
-            m_xml.scopedElement( "OverallResults" )
-                .writeAttribute( "successes", totals.assertions.passed )
-                .writeAttribute( "failures", totals.assertions.failed )
-                .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
-            m_xml.endElement();
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) {
+            StreamingReporterBase::testCaseStarting(testInfo);
+            m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                m_testCaseTimer.start();
         }
 
-        virtual void StartSection( const std::string& sectionName, const std::string& description ) {
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) {
+            StreamingReporterBase::sectionStarting( sectionInfo );
             if( m_sectionDepth++ > 0 ) {
                 m_xml.startElement( "Section" )
-                    .writeAttribute( "name", trim( sectionName ) )
-                    .writeAttribute( "description", description );
+                    .writeAttribute( "name", trim( sectionInfo.name ) )
+                    .writeAttribute( "description", sectionInfo.description );
             }
         }
-        virtual void NoAssertionsInSection( const std::string& ) {}
-        virtual void NoAssertionsInTestCase( const std::string& ) {}
 
-        virtual void EndSection( const std::string& /*sectionName*/, const Counts& assertions ) {
-            if( --m_sectionDepth > 0 ) {
-                m_xml.scopedElement( "OverallResults" )
-                    .writeAttribute( "successes", assertions.passed )
-                    .writeAttribute( "failures", assertions.failed )
-                    .writeAttribute( "expectedFailures", assertions.failedButOk );
-                m_xml.endElement();
-            }
-        }
+        virtual void assertionStarting( AssertionInfo const& ) { }
 
-        virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) {
-            m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
-            m_currentTestSuccess = true;
-        }
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+            const AssertionResult& assertionResult = assertionStats.assertionResult;
 
-        virtual void Result( const Catch::AssertionResult& assertionResult ) {
-            if( !m_config.fullConfig()->includeSuccessfulResults() && assertionResult.getResultType() == ResultWas::Ok )
-                return;
+            // Print any info messages in <Info> tags.
+            if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+                for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+                        it != itEnd;
+                        ++it ) {
+                    if( it->type == ResultWas::Info ) {
+                        m_xml.scopedElement( "Info" )
+                            .writeText( it->message );
+                    } else if ( it->type == ResultWas::Warning ) {
+                        m_xml.scopedElement( "Warning" )
+                            .writeText( it->message );
+                    }
+                }
+            }
 
+            // Drop out if result was successful but we're not printing them.
+            if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+                return true;
+
+            // Print the expression if there is one.
             if( assertionResult.hasExpression() ) {
                 m_xml.startElement( "Expression" )
                     .writeAttribute( "success", assertionResult.succeeded() )
+					.writeAttribute( "type", assertionResult.getTestMacroName() )
                     .writeAttribute( "filename", assertionResult.getSourceInfo().file )
                     .writeAttribute( "line", assertionResult.getSourceInfo().line );
 
@@ -7765,58 +8138,96 @@ namespace Catch {
                     .writeText( assertionResult.getExpression() );
                 m_xml.scopedElement( "Expanded" )
                     .writeText( assertionResult.getExpandedExpression() );
-                m_currentTestSuccess &= assertionResult.succeeded();
             }
 
+            // And... Print a result applicable to each result type.
             switch( assertionResult.getResultType() ) {
                 case ResultWas::ThrewException:
                     m_xml.scopedElement( "Exception" )
                         .writeAttribute( "filename", assertionResult.getSourceInfo().file )
                         .writeAttribute( "line", assertionResult.getSourceInfo().line )
                         .writeText( assertionResult.getMessage() );
-                    m_currentTestSuccess = false;
+                    break;
+                case ResultWas::FatalErrorCondition:
+                    m_xml.scopedElement( "Fatal Error Condition" )
+                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
+                        .writeText( assertionResult.getMessage() );
                     break;
                 case ResultWas::Info:
                     m_xml.scopedElement( "Info" )
                         .writeText( assertionResult.getMessage() );
                     break;
                 case ResultWas::Warning:
-                    m_xml.scopedElement( "Warning" )
-                        .writeText( assertionResult.getMessage() );
+                    // Warning will already have been written
                     break;
                 case ResultWas::ExplicitFailure:
                     m_xml.scopedElement( "Failure" )
                         .writeText( assertionResult.getMessage() );
-                    m_currentTestSuccess = false;
                     break;
-                case ResultWas::Unknown:
-                case ResultWas::Ok:
-                case ResultWas::FailureBit:
-                case ResultWas::ExpressionFailed:
-                case ResultWas::Exception:
-                case ResultWas::DidntThrowException:
+                default:
                     break;
             }
+
             if( assertionResult.hasExpression() )
                 m_xml.endElement();
+
+            return true;
         }
 
-        virtual void Aborted() {
-            // !TBD
+        virtual void sectionEnded( SectionStats const& sectionStats ) {
+            StreamingReporterBase::sectionEnded( sectionStats );
+            if( --m_sectionDepth > 0 ) {
+                XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+                e.writeAttribute( "successes", sectionStats.assertions.passed );
+                e.writeAttribute( "failures", sectionStats.assertions.failed );
+                e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+                if ( m_config->showDurations() == ShowDurations::Always )
+                    e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+                m_xml.endElement();
+            }
         }
 
-        virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string&, const std::string& ) {
-            m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess );
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+            StreamingReporterBase::testCaseEnded( testCaseStats );
+            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+            e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+            m_xml.endElement();
+        }
+
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+            StreamingReporterBase::testGroupEnded( testGroupStats );
+            // TODO: Check testGroupStats.aborting and act accordingly.
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+                .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+            m_xml.endElement();
+        }
+
+        virtual void testRunEnded( TestRunStats const& testRunStats ) {
+            StreamingReporterBase::testRunEnded( testRunStats );
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+                .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
             m_xml.endElement();
         }
 
     private:
-        ReporterConfig m_config;
-        bool m_currentTestSuccess;
+        Timer m_testCaseTimer;
         XmlWriter m_xml;
         int m_sectionDepth;
     };
 
+     INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
 } // end namespace Catch
 
 // #included from: ../reporters/catch_reporter_junit.hpp
@@ -7943,7 +8354,7 @@ namespace Catch {
                     xml.writeAttribute( "classname", className );
                     xml.writeAttribute( "name", name );
                 }
-                xml.writeAttribute( "time", toString( sectionNode.stats.durationInSeconds ) );
+                xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
 
                 writeAssertions( sectionNode );
 
@@ -7976,6 +8387,7 @@ namespace Catch {
                 std::string elementName;
                 switch( result.getResultType() ) {
                     case ResultWas::ThrewException:
+                    case ResultWas::FatalErrorCondition:
                         elementName = "error";
                         break;
                     case ResultWas::ExplicitFailure:
@@ -8034,8 +8446,6 @@ namespace Catch {
 // #included from: ../reporters/catch_reporter_console.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
 
-#include <cstring>
-
 namespace Catch {
 
     struct ConsoleReporter : StreamingReporterBase {
@@ -8170,6 +8580,11 @@ namespace Catch {
                         passOrFail = "FAILED";
                         messageLabel = "due to unexpected exception with message";
                         break;
+                    case ResultWas::FatalErrorCondition:
+                        colour = Colour::Error;
+                        passOrFail = "FAILED";
+                        messageLabel = "due to a fatal error condition";
+                        break;
                     case ResultWas::DidntThrowException:
                         colour = Colour::Error;
                         passOrFail = "FAILED";
@@ -8279,14 +8694,12 @@ namespace Catch {
             stream  << "\n" << getLineOfChars<'~'>() << "\n";
             Colour colour( Colour::SecondaryText );
             stream  << currentTestRunInfo->name
-                    << " is a Catch v"  << libraryVersion.majorVersion << "."
-                    << libraryVersion.minorVersion << " b"
-                    << libraryVersion.buildNumber;
-            if( libraryVersion.branchName != std::string( "master" ) )
-                stream << " (" << libraryVersion.branchName << ")";
-            stream  << " host application.\n"
+                    << " is a Catch v"  << libraryVersion << " host application.\n"
                     << "Run with -? for options\n\n";
 
+            if( m_config->rngSeed() != 0 )
+                stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
             currentTestRunInfo.used = true;
         }
         void lazyPrintGroupInfo() {
@@ -8458,15 +8871,6 @@ namespace Catch {
         void printSummaryDivider() {
             stream << getLineOfChars<'-'>() << "\n";
         }
-        template<char C>
-        static char const* getLineOfChars() {
-            static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
-            if( !*line ) {
-                memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
-                line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
-            }
-            return line;
-        }
 
     private:
         bool m_headerPrinted;
@@ -8575,6 +8979,13 @@ namespace Catch {
                         printExpressionWas();
                         printRemainingMessages();
                         break;
+                    case ResultWas::FatalErrorCondition:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "fatal error condition with message:" );
+                        printMessage();
+                        printExpressionWas();
+                        printRemainingMessages();
+                        break;
                     case ResultWas::DidntThrowException:
                         printResultType( Colour::Error, failedString() );
                         printIssue( "expected exception, got none" );
@@ -8804,8 +9215,6 @@ namespace Catch {
     Matchers::Impl::StdString::EndsWith::~EndsWith() {}
 
     void Config::dummy() {}
-
-    INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
 }
 
 #ifdef __clang__
@@ -8994,9 +9403,13 @@ using Catch::Detail::Approx;
 #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
 
 #ifdef __clang__
-#pragma clang diagnostic pop
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
 #elif defined __GNUC__
-#pragma GCC diagnostic pop
+#    pragma GCC diagnostic pop
 #endif
 
 #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
diff --git a/test/include/catch_orig.hpp b/test/include/catch_orig.hpp
index 6b8dfb5..de61226 100644
--- a/test/include/catch_orig.hpp
+++ b/test/include/catch_orig.hpp
@@ -1,6 +1,6 @@
 /*
- *  CATCH v1.0 build 53 (master branch)
- *  Generated: 2014-08-20 08:08:19.533804
+ *  Catch v1.2.1
+ *  Generated: 2015-06-30 18:23:27.961086
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -13,31 +13,43 @@
 
 #define TWOBLUECUBES_CATCH_HPP_INCLUDED
 
+#ifdef __clang__
+#    pragma clang system_header
+#elif defined __GNUC__
+#    pragma GCC system_header
+#endif
+
 // #included from: internal/catch_suppress_warnings.h
 
 #define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED
 
 #ifdef __clang__
-#pragma clang diagnostic ignored "-Wglobal-constructors"
-#pragma clang diagnostic ignored "-Wvariadic-macros"
-#pragma clang diagnostic ignored "-Wc99-extensions"
-#pragma clang diagnostic ignored "-Wunused-variable"
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wpadded"
-#pragma clang diagnostic ignored "-Wc++98-compat"
-#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#   ifdef __ICC // icpc defines the __clang__ macro
+#       pragma warning(push)
+#       pragma warning(disable: 161 1682)
+#   else // __ICC
+#       pragma clang diagnostic ignored "-Wglobal-constructors"
+#       pragma clang diagnostic ignored "-Wvariadic-macros"
+#       pragma clang diagnostic ignored "-Wc99-extensions"
+#       pragma clang diagnostic ignored "-Wunused-variable"
+#       pragma clang diagnostic push
+#       pragma clang diagnostic ignored "-Wpadded"
+#       pragma clang diagnostic ignored "-Wc++98-compat"
+#       pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#       pragma clang diagnostic ignored "-Wswitch-enum"
+#    endif
 #elif defined __GNUC__
-#pragma GCC diagnostic ignored "-Wvariadic-macros"
-#pragma GCC diagnostic ignored "-Wunused-variable"
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpadded"
+#    pragma GCC diagnostic ignored "-Wvariadic-macros"
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wpadded"
 #endif
 
-#ifdef CATCH_CONFIG_MAIN
-#  define CATCH_CONFIG_RUNNER
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+#  define CATCH_IMPL
 #endif
 
-#ifdef CATCH_CONFIG_RUNNER
+#ifdef CATCH_IMPL
 #  ifndef CLARA_CONFIG_MAIN
 #    define CLARA_CONFIG_MAIN_NOT_DEFINED
 #    define CLARA_CONFIG_MAIN
@@ -64,16 +76,34 @@
 // #included from: catch_compiler_capabilities.h
 #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
 
-// Much of the following code is based on Boost (1.53)
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
+// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
+
+// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
 
 #ifdef __clang__
 
 #  if __has_feature(cxx_nullptr)
-#    define CATCH_CONFIG_CPP11_NULLPTR
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #  endif
 
 #  if __has_feature(cxx_noexcept)
-#    define CATCH_CONFIG_CPP11_NOEXCEPT
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 #  endif
 
 #endif // __clang__
@@ -82,51 +112,26 @@
 // Borland
 #ifdef __BORLANDC__
 
-#if (__BORLANDC__ > 0x582 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __BORLANDC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // EDG
 #ifdef __EDG_VERSION__
 
-#if (__EDG_VERSION__ > 238 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __EDG_VERSION__
 
 ////////////////////////////////////////////////////////////////////////////////
 // Digital Mars
 #ifdef __DMC__
 
-#if (__DMC__ > 0x840 )
-//#define CATCH_CONFIG_SFINAE // Not confirmed
-#endif
-
 #endif // __DMC__
 
 ////////////////////////////////////////////////////////////////////////////////
 // GCC
 #ifdef __GNUC__
 
-#if __GNUC__ < 3
-
-#if (__GNUC_MINOR__ >= 96 )
-//#define CATCH_CONFIG_SFINAE
-#endif
-
-#elif __GNUC__ >= 3
-
-// #define CATCH_CONFIG_SFINAE // Taking this out completely for now
-
-#endif // __GNUC__ < 3
-
 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) )
-
-#define CATCH_CONFIG_CPP11_NULLPTR
+#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #endif
 
 #endif // __GNUC__
@@ -135,8 +140,13 @@
 // Visual C++
 #ifdef _MSC_VER
 
-#if (_MSC_VER >= 1310 ) // (VC++ 7.0+)
-//#define CATCH_CONFIG_SFINAE // Not confirmed
+#if (_MSC_VER >= 1600)
+#   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
 #endif
 
 #endif // _MSC_VER
@@ -147,21 +157,62 @@
     ( defined __GNUC__ && __GNUC__ >= 3 ) || \
     ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
 
-#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS
-#define CATCH_CONFIG_VARIADIC_MACROS
-#endif
+#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
 
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
 // C++ language feature support
 
-// detect language version:
-#if (__cplusplus == 201103L)
-#  define CATCH_CPP11
-#  define CATCH_CPP11_OR_GREATER
-#elif (__cplusplus >= 201103L)
+// catch all support for C++11
+#if (__cplusplus >= 201103L)
+
 #  define CATCH_CPP11_OR_GREATER
+
+#  if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR)
+#    define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#    define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#    define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#    define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#    define CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+#  endif
+
+#  ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#    define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+#  endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_IS_ENUM
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_TUPLE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
+#define CATCH_CONFIG_VARIADIC_MACROS
 #endif
 
 // noexcept support:
@@ -176,8 +227,16 @@
 namespace Catch {
 
     class NonCopyable {
-        NonCopyable( NonCopyable const& );
-        void operator = ( NonCopyable const& );
+#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        NonCopyable( NonCopyable const& )              = delete;
+        NonCopyable( NonCopyable && )                  = delete;
+        NonCopyable& operator = ( NonCopyable const& ) = delete;
+        NonCopyable& operator = ( NonCopyable && )     = delete;
+#else
+        NonCopyable( NonCopyable const& info );
+        NonCopyable& operator = ( NonCopyable const& );
+#endif
+
     protected:
         NonCopyable() {}
         virtual ~NonCopyable();
@@ -215,6 +274,7 @@ namespace Catch {
     void toLowerInPlace( std::string& s );
     std::string toLower( std::string const& s );
     std::string trim( std::string const& str );
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
 
     struct pluralise {
         pluralise( std::size_t count, std::string const& label );
@@ -230,13 +290,14 @@ namespace Catch {
         SourceLineInfo();
         SourceLineInfo( char const* _file, std::size_t _line );
         SourceLineInfo( SourceLineInfo const& other );
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         SourceLineInfo( SourceLineInfo && )                  = default;
         SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
         SourceLineInfo& operator = ( SourceLineInfo && )     = default;
 #  endif
         bool empty() const;
         bool operator == ( SourceLineInfo const& other ) const;
+        bool operator < ( SourceLineInfo const& other ) const;
 
         std::string file;
         std::size_t line;
@@ -467,7 +528,7 @@ namespace Catch {
     struct ITestCaseRegistry {
         virtual ~ITestCaseRegistry();
         virtual std::vector<TestCase> const& getAllTests() const = 0;
-        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases ) const = 0;
+        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const = 0;
 
     };
 }
@@ -603,7 +664,9 @@ namespace Catch {
         Exception = 0x100 | FailureBit,
 
         ThrewException = Exception | 1,
-        DidntThrowException = Exception | 2
+        DidntThrowException = Exception | 2,
+
+        FatalErrorCondition = 0x200 | FailureBit
 
     }; };
 
@@ -616,11 +679,11 @@ namespace Catch {
 
     // ResultDisposition::Flags enum
     struct ResultDisposition { enum Flags {
-        Normal = 0x00,
+        Normal = 0x01,
 
-        ContinueOnFailure = 0x01,   // Failures fail test, but execution continues
-        FalseTest = 0x02,           // Prefix expression with !
-        SuppressFail = 0x04         // Failures are reported but do not fail the test
+        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
+        FalseTest = 0x04,           // Prefix expression with !
+        SuppressFail = 0x08         // Failures are reported but do not fail the test
     }; };
 
     inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
@@ -668,7 +731,7 @@ namespace Catch {
         AssertionResult();
         AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
         ~AssertionResult();
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
          AssertionResult( AssertionResult const& )              = default;
          AssertionResult( AssertionResult && )                  = default;
          AssertionResult& operator = ( AssertionResult const& ) = default;
@@ -724,8 +787,8 @@ namespace Catch {
                         ResultDisposition::Flags resultDisposition );
 
         template<typename T>
-        ExpressionLhs<T const&> operator->* ( T const& operand );
-        ExpressionLhs<bool> operator->* ( bool value );
+        ExpressionLhs<T const&> operator <= ( T const& operand );
+        ExpressionLhs<bool> operator <= ( bool value );
 
         template<typename T>
         ResultBuilder& operator << ( T const& value ) {
@@ -948,40 +1011,6 @@ namespace Internal {
 // #included from: catch_tostring.h
 #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
 
-// #included from: catch_sfinae.hpp
-#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED
-
-// Try to detect if the current compiler supports SFINAE
-
-namespace Catch {
-
-    struct TrueType {
-        static const bool value = true;
-        typedef void Enable;
-        char sizer[1];
-    };
-    struct FalseType {
-        static const bool value = false;
-        typedef void Disable;
-        char sizer[2];
-    };
-
-#ifdef CATCH_CONFIG_SFINAE
-
-    template<bool> struct NotABooleanExpression;
-
-    template<bool c> struct If : NotABooleanExpression<c> {};
-    template<> struct If<true> : TrueType {};
-    template<> struct If<false> : FalseType {};
-
-    template<int size> struct SizedIf;
-    template<> struct SizedIf<sizeof(TrueType)> : TrueType {};
-    template<> struct SizedIf<sizeof(FalseType)> : FalseType {};
-
-#endif // CATCH_CONFIG_SFINAE
-
-} // end namespace Catch
-
 #include <sstream>
 #include <iomanip>
 #include <limits>
@@ -1034,35 +1063,59 @@ inline id performOptionalSelector( id obj, SEL sel ) {
 
 #endif
 
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+#include <tuple>
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_IS_ENUM
+#include <type_traits>
+#endif
+
 namespace Catch {
-namespace Detail {
 
-// SFINAE is currently disabled by default for all compilers.
-// If the non SFINAE version of IsStreamInsertable is ambiguous for you
-// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE
-#ifdef CATCH_CONFIG_SFINAE
+// Why we're here.
+template<typename T>
+std::string toString( T const& value );
 
-    template<typename T>
-    class IsStreamInsertableHelper {
-        template<int N> struct TrueIfSizeable : TrueType {};
+// Built in overloads
 
-        template<typename T2>
-        static TrueIfSizeable<sizeof((*(std::ostream*)0) << *((T2 const*)0))> dummy(T2*);
-        static FalseType dummy(...);
+std::string toString( std::string const& value );
+std::string toString( std::wstring const& value );
+std::string toString( const char* const value );
+std::string toString( char* const value );
+std::string toString( const wchar_t* const value );
+std::string toString( wchar_t* const value );
+std::string toString( int value );
+std::string toString( unsigned long value );
+std::string toString( unsigned int value );
+std::string toString( const double value );
+std::string toString( const float value );
+std::string toString( bool value );
+std::string toString( char value );
+std::string toString( signed char value );
+std::string toString( unsigned char value );
 
-    public:
-        typedef SizedIf<sizeof(dummy((T*)0))> type;
-    };
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t );
+#endif
 
-    template<typename T>
-    struct IsStreamInsertable : IsStreamInsertableHelper<T>::type {};
+#ifdef __OBJC__
+    std::string toString( NSString const * const& nsstring );
+    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+    std::string toString( NSObject* const& nsObject );
+#endif
 
-#else
+namespace Detail {
+
+    extern std::string unprintableString;
 
     struct BorgType {
         template<typename T> BorgType( T const& );
     };
 
+    struct TrueType { char sizer[1]; };
+    struct FalseType { char sizer[2]; };
+
     TrueType& testStreamable( std::ostream& );
     FalseType testStreamable( FalseType );
 
@@ -1075,12 +1128,38 @@ namespace Detail {
         enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
     };
 
-#endif
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+    template<typename T,
+             bool IsEnum = std::is_enum<T>::value
+             >
+    struct EnumStringMaker
+    {
+        static std::string convert( T const& ) { return unprintableString; }
+    };
 
+    template<typename T>
+    struct EnumStringMaker<T,true>
+    {
+        static std::string convert( T const& v )
+        {
+            return ::Catch::toString(
+                static_cast<typename std::underlying_type<T>::type>(v)
+                );
+        }
+    };
+#endif
     template<bool C>
     struct StringMakerBase {
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+        template<typename T>
+        static std::string convert( T const& v )
+        {
+            return EnumStringMaker<T>::convert( v );
+        }
+#else
         template<typename T>
-        static std::string convert( T const& ) { return "{?}"; }
+        static std::string convert( T const& ) { return unprintableString; }
+#endif
     };
 
     template<>
@@ -1103,9 +1182,6 @@ namespace Detail {
 } // end namespace Detail
 
 template<typename T>
-std::string toString( T const& value );
-
-template<typename T>
 struct StringMaker :
     Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
 
@@ -1135,12 +1211,59 @@ namespace Detail {
     std::string rangeToString( InputIterator first, InputIterator last );
 }
 
+//template<typename T, typename Allocator>
+//struct StringMaker<std::vector<T, Allocator> > {
+//    static std::string convert( std::vector<T,Allocator> const& v ) {
+//        return Detail::rangeToString( v.begin(), v.end() );
+//    }
+//};
+
 template<typename T, typename Allocator>
-struct StringMaker<std::vector<T, Allocator> > {
-    static std::string convert( std::vector<T,Allocator> const& v ) {
-        return Detail::rangeToString( v.begin(), v.end() );
+std::string toString( std::vector<T,Allocator> const& v ) {
+    return Detail::rangeToString( v.begin(), v.end() );
+}
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+
+// toString for tuples
+namespace TupleDetail {
+  template<
+      typename Tuple,
+      std::size_t N = 0,
+      bool = (N < std::tuple_size<Tuple>::value)
+      >
+  struct ElementPrinter {
+      static void print( const Tuple& tuple, std::ostream& os )
+      {
+          os << ( N ? ", " : " " )
+             << Catch::toString(std::get<N>(tuple));
+          ElementPrinter<Tuple,N+1>::print(tuple,os);
+      }
+  };
+
+  template<
+      typename Tuple,
+      std::size_t N
+      >
+  struct ElementPrinter<Tuple,N,false> {
+      static void print( const Tuple&, std::ostream& ) {}
+  };
+
+}
+
+template<typename ...Types>
+struct StringMaker<std::tuple<Types...>> {
+
+    static std::string convert( const std::tuple<Types...>& tuple )
+    {
+        std::ostringstream os;
+        os << '{';
+        TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
+        os << " }";
+        return os.str();
     }
 };
+#endif // CATCH_CONFIG_CPP11_TUPLE
 
 namespace Detail {
     template<typename T>
@@ -1161,44 +1284,15 @@ std::string toString( T const& value ) {
     return StringMaker<T>::convert( value );
 }
 
-// Built in overloads
-
-std::string toString( std::string const& value );
-std::string toString( std::wstring const& value );
-std::string toString( const char* const value );
-std::string toString( char* const value );
-std::string toString( const wchar_t* const value );
-std::string toString( wchar_t* const value );
-std::string toString( int value );
-std::string toString( unsigned long value );
-std::string toString( unsigned int value );
-std::string toString( const double value );
-std::string toString( const float value );
-std::string toString( bool value );
-std::string toString( char value );
-std::string toString( signed char value );
-std::string toString( unsigned char value );
-
-#ifdef CATCH_CONFIG_CPP11_NULLPTR
-std::string toString( std::nullptr_t );
-#endif
-
-#ifdef __OBJC__
-    std::string toString( NSString const * const& nsstring );
-    std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
-    std::string toString( NSObject* const& nsObject );
-#endif
-
     namespace Detail {
     template<typename InputIterator>
     std::string rangeToString( InputIterator first, InputIterator last ) {
         std::ostringstream oss;
         oss << "{ ";
         if( first != last ) {
-            oss << toString( *first );
-            for( ++first ; first != last ; ++first ) {
-                oss << ", " << toString( *first );
-            }
+            oss << Catch::toString( *first );
+            for( ++first ; first != last ; ++first )
+                oss << ", " << Catch::toString( *first );
         }
         oss << " }";
         return oss.str();
@@ -1214,13 +1308,13 @@ namespace Catch {
 template<typename T>
 class ExpressionLhs {
     ExpressionLhs& operator = ( ExpressionLhs const& );
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
     ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
 #  endif
 
 public:
     ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
     ExpressionLhs( ExpressionLhs const& ) = default;
     ExpressionLhs( ExpressionLhs && )     = default;
 #  endif
@@ -1301,11 +1395,11 @@ private:
 namespace Catch {
 
     template<typename T>
-    inline ExpressionLhs<T const&> ResultBuilder::operator->* ( T const& operand ) {
+    inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
         return ExpressionLhs<T const&>( *this, operand );
     }
 
-    inline ExpressionLhs<bool> ResultBuilder::operator->* ( bool value ) {
+    inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) {
         return ExpressionLhs<bool>( *this, value );
     }
 
@@ -1395,6 +1489,8 @@ namespace Catch {
 
         virtual std::string getCurrentTestName() const = 0;
         virtual const AssertionResult* getLastResult() const = 0;
+
+        virtual void handleFatalErrorCondition( std::string const& message ) = 0;
     };
 
     IResultCapture& getResultCapture();
@@ -1475,7 +1571,7 @@ namespace Catch {
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
-            ( __catchResult->*expr ).endExpression(); \
+            ( __catchResult <= expr ).endExpression(); \
         } \
         catch( ... ) { \
             __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
@@ -1575,7 +1671,7 @@ namespace Catch {
             std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \
             __catchResult \
                 .setLhs( Catch::toString( arg ) ) \
-                .setRhs( matcherAsString == "{?}" ? #matcher : matcherAsString ) \
+                .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
                 .setOp( "matches" ) \
                 .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \
             __catchResult.captureExpression(); \
@@ -1636,6 +1732,9 @@ namespace Catch {
         bool allPassed() const {
             return failed == 0 && failedButOk == 0;
         }
+        bool allOk() const {
+            return failed == 0;
+        }
 
         std::size_t passed;
         std::size_t failed;
@@ -1688,7 +1787,7 @@ namespace Catch {
     public:
         Timer() : m_ticks( 0 ) {}
         void start();
-        unsigned int getElapsedNanoseconds() const;
+        unsigned int getElapsedMicroseconds() const;
         unsigned int getElapsedMilliseconds() const;
         double getElapsedSeconds() const;
 
@@ -1702,7 +1801,7 @@ namespace Catch {
 
 namespace Catch {
 
-    class Section {
+    class Section : NonCopyable {
     public:
         Section( SectionInfo const& info );
         ~Section();
@@ -1711,15 +1810,6 @@ namespace Catch {
         operator bool() const;
 
     private:
-#ifdef CATCH_CPP11_OR_GREATER
-        Section( Section const& )              = delete;
-        Section( Section && )                  = delete;
-        Section& operator = ( Section const& ) = delete;
-        Section& operator = ( Section && )     = delete;
-#else
-        Section( Section const& info );
-        Section& operator = ( Section const& );
-#endif
         SectionInfo m_info;
 
         std::string m_name;
@@ -2694,7 +2784,7 @@ return @ desc; \
 
 #endif
 
-#ifdef CATCH_CONFIG_RUNNER
+#ifdef CATCH_IMPL
 // #included from: internal/catch_impl.hpp
 #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
 
@@ -2706,7 +2796,7 @@ return @ desc; \
 #pragma clang diagnostic ignored "-Wweak-vtables"
 #endif
 
-// #included from: catch_runner.hpp
+// #included from: ../catch_runner.hpp
 #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
 
 // #included from: internal/catch_commandline.hpp
@@ -2962,6 +3052,11 @@ namespace Catch {
         Always,
         Never
     }; };
+    struct RunTests { enum InWhatOrder {
+        InDeclarationOrder,
+        InLexicographicalOrder,
+        InRandomOrder
+    }; };
 
     class TestSpec;
 
@@ -2979,6 +3074,9 @@ namespace Catch {
         virtual bool showInvisibles() const = 0;
         virtual ShowDurations::OrNot showDurations() const = 0;
         virtual TestSpec const& testSpec() const = 0;
+        virtual RunTests::InWhatOrder runOrder() const = 0;
+        virtual unsigned int rngSeed() const = 0;
+        virtual bool forceColour() const = 0;
     };
 }
 
@@ -3004,12 +3102,16 @@ namespace Catch {
     private:
         bool isOwned;
     };
+
+    std::ostream& cout();
+    std::ostream& cerr();
 }
 
 #include <memory>
 #include <vector>
 #include <string>
 #include <iostream>
+#include <ctime>
 
 #ifndef CATCH_CONFIG_CONSOLE_WIDTH
 #define CATCH_CONFIG_CONSOLE_WIDTH 80
@@ -3029,10 +3131,13 @@ namespace Catch {
             noThrow( false ),
             showHelp( false ),
             showInvisibles( false ),
+            forceColour( false ),
             abortAfter( -1 ),
+            rngSeed( 0 ),
             verbosity( Verbosity::Normal ),
             warnings( WarnAbout::Nothing ),
-            showDurations( ShowDurations::DefaultForReporter )
+            showDurations( ShowDurations::DefaultForReporter ),
+            runOrder( RunTests::InDeclarationOrder )
         {}
 
         bool listTests;
@@ -3045,12 +3150,15 @@ namespace Catch {
         bool noThrow;
         bool showHelp;
         bool showInvisibles;
+        bool forceColour;
 
         int abortAfter;
+        unsigned int rngSeed;
 
         Verbosity::Level verbosity;
         WarnAbout::What warnings;
         ShowDurations::OrNot showDurations;
+        RunTests::InWhatOrder runOrder;
 
         std::string reporterName;
         std::string outputFilename;
@@ -3068,12 +3176,12 @@ namespace Catch {
     public:
 
         Config()
-        :   m_os( std::cout.rdbuf() )
+        :   m_os( Catch::cout().rdbuf() )
         {}
 
         Config( ConfigData const& data )
         :   m_data( data ),
-            m_os( std::cout.rdbuf() )
+            m_os( Catch::cout().rdbuf() )
         {
             if( !data.testsOrTags.empty() ) {
                 TestSpecParser parser( ITagAliasRegistry::get() );
@@ -3084,7 +3192,7 @@ namespace Catch {
         }
 
         virtual ~Config() {
-            m_os.rdbuf( std::cout.rdbuf() );
+            m_os.rdbuf( Catch::cout().rdbuf() );
             m_stream.release();
         }
 
@@ -3106,7 +3214,7 @@ namespace Catch {
         bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
 
         void setStreamBuf( std::streambuf* buf ) {
-            m_os.rdbuf( buf ? buf : std::cout.rdbuf() );
+            m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() );
         }
 
         void useStream( std::string const& streamName ) {
@@ -3132,6 +3240,9 @@ namespace Catch {
         virtual bool includeSuccessfulResults() const   { return m_data.showSuccessfulTests; }
         virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
         virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
+        virtual RunTests::InWhatOrder runOrder() const  { return m_data.runOrder; }
+        virtual unsigned int rngSeed() const    { return m_data.rngSeed; }
+        virtual bool forceColour() const { return m_data.forceColour; }
 
     private:
         ConfigData m_data;
@@ -3393,7 +3504,7 @@ namespace Clara {
         template<typename ConfigT>
         struct IArgFunction {
             virtual ~IArgFunction() {}
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
             IArgFunction()                      = default;
             IArgFunction( IArgFunction const& ) = default;
 #  endif
@@ -3760,7 +3871,7 @@ namespace Clara {
             m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
         {
             if( other.m_floatingArg.get() )
-                m_floatingArg = ArgAutoPtr( new Arg( *other.m_floatingArg ) );
+                m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
         }
 
         CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
@@ -3788,7 +3899,7 @@ namespace Clara {
         ArgBuilder operator[]( UnpositionalTag ) {
             if( m_floatingArg.get() )
                 throw std::logic_error( "Only one unpositional argument can be added" );
-            m_floatingArg = ArgAutoPtr( new Arg() );
+            m_floatingArg.reset( new Arg() );
             ArgBuilder builder( m_floatingArg.get() );
             return builder;
         }
@@ -3930,7 +4041,7 @@ namespace Clara {
                 if( it == itEnd ) {
                     if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
                         unusedTokens.push_back( token );
-                    else if( m_throwOnUnrecognisedTokens )
+                    else if( errors.empty() && m_throwOnUnrecognisedTokens )
                         errors.push_back( "unrecognised option: " + token.data );
                 }
             }
@@ -4028,7 +4139,28 @@ namespace Catch {
             config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
         else
             throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
-
+    }
+    inline void setOrder( ConfigData& config, std::string const& order ) {
+        if( startsWith( "declared", order ) )
+            config.runOrder = RunTests::InDeclarationOrder;
+        else if( startsWith( "lexical", order ) )
+            config.runOrder = RunTests::InLexicographicalOrder;
+        else if( startsWith( "random", order ) )
+            config.runOrder = RunTests::InRandomOrder;
+        else
+            throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+    }
+    inline void setRngSeed( ConfigData& config, std::string const& seed ) {
+        if( seed == "time" ) {
+            config.rngSeed = static_cast<unsigned int>( std::time(0) );
+        }
+        else {
+            std::stringstream ss;
+            ss << seed;
+            ss >> config.rngSeed;
+            if( ss.fail() )
+                throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+        }
     }
     inline void setVerbosity( ConfigData& config, int level ) {
         // !TBD: accept strings?
@@ -4140,6 +4272,18 @@ namespace Catch {
             .describe( "list all reporters" )
             .bind( &ConfigData::listReporters );
 
+        cli["--order"]
+            .describe( "test case order (defaults to decl)" )
+            .bind( &setOrder, "decl|lex|rand" );
+
+        cli["--rng-seed"]
+            .describe( "set a specific seed for random numbers" )
+            .bind( &setRngSeed, "'time'|number" );
+
+        cli["--force-colour"]
+            .describe( "force colourised output" )
+            .bind( &ConfigData::forceColour );
+
         return cli;
     }
 
@@ -4313,10 +4457,6 @@ namespace Catch {
 
 namespace Catch {
 
-    namespace Detail {
-        struct IColourImpl;
-    }
-
     struct Colour {
         enum Code {
             None = 0,
@@ -4362,7 +4502,6 @@ namespace Catch {
         static void use( Code _colourCode );
 
     private:
-        static Detail::IColourImpl* impl();
         bool m_moved;
     };
 
@@ -4456,7 +4595,7 @@ namespace Catch
         }
         virtual ~AssertionStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         AssertionStats( AssertionStats const& )              = default;
         AssertionStats( AssertionStats && )                  = default;
         AssertionStats& operator = ( AssertionStats const& ) = default;
@@ -4479,7 +4618,7 @@ namespace Catch
             missingAssertions( _missingAssertions )
         {}
         virtual ~SectionStats();
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         SectionStats( SectionStats const& )              = default;
         SectionStats( SectionStats && )                  = default;
         SectionStats& operator = ( SectionStats const& ) = default;
@@ -4506,7 +4645,7 @@ namespace Catch
         {}
         virtual ~TestCaseStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestCaseStats( TestCaseStats const& )              = default;
         TestCaseStats( TestCaseStats && )                  = default;
         TestCaseStats& operator = ( TestCaseStats const& ) = default;
@@ -4534,7 +4673,7 @@ namespace Catch
         {}
         virtual ~TestGroupStats();
 
-#  ifdef CATCH_CPP11_OR_GREATER
+#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestGroupStats( TestGroupStats const& )              = default;
         TestGroupStats( TestGroupStats && )                  = default;
         TestGroupStats& operator = ( TestGroupStats const& ) = default;
@@ -4556,7 +4695,7 @@ namespace Catch
         {}
         virtual ~TestRunStats();
 
-#  ifndef CATCH_CPP11_OR_GREATER
+#  ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
         TestRunStats( TestRunStats const& _other )
         :   runInfo( _other.runInfo ),
             totals( _other.totals ),
@@ -4592,11 +4731,14 @@ namespace Catch
 
         virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
 
+        // The return value indicates if the messages buffer should be cleared:
         virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
         virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
         virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
         virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
         virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
     };
 
     struct IReporterFactory {
@@ -4624,9 +4766,9 @@ namespace Catch {
 
         TestSpec testSpec = config.testSpec();
         if( config.testSpec().hasFilters() )
-            std::cout << "Matching test cases:\n";
+            Catch::cout() << "Matching test cases:\n";
         else {
-            std::cout << "All available test cases:\n";
+            Catch::cout() << "All available test cases:\n";
             testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
         }
 
@@ -4647,15 +4789,15 @@ namespace Catch {
                 : Colour::None;
             Colour colourGuard( colour );
 
-            std::cout << Text( testCaseInfo.name, nameAttr ) << std::endl;
+            Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
             if( !testCaseInfo.tags.empty() )
-                std::cout << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
+                Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
         }
 
         if( !config.testSpec().hasFilters() )
-            std::cout << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
         else
-            std::cout << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
         return matchedTests;
     }
 
@@ -4671,7 +4813,7 @@ namespace Catch {
                 ++it ) {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
-            std::cout << testCaseInfo.name << std::endl;
+            Catch::cout() << testCaseInfo.name << std::endl;
         }
         return matchedTests;
     }
@@ -4697,9 +4839,9 @@ namespace Catch {
     inline std::size_t listTags( Config const& config ) {
         TestSpec testSpec = config.testSpec();
         if( config.testSpec().hasFilters() )
-            std::cout << "Tags for matching test cases:\n";
+            Catch::cout() << "Tags for matching test cases:\n";
         else {
-            std::cout << "All available tags:\n";
+            Catch::cout() << "All available tags:\n";
             testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
         }
 
@@ -4733,14 +4875,14 @@ namespace Catch {
                                                     .setInitialIndent( 0 )
                                                     .setIndent( oss.str().size() )
                                                     .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
-            std::cout << oss.str() << wrapper << "\n";
+            Catch::cout() << oss.str() << wrapper << "\n";
         }
-        std::cout << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
         return tagCounts.size();
     }
 
     inline std::size_t listReporters( Config const& /*config*/ ) {
-        std::cout << "Available reports:\n";
+        Catch::cout() << "Available reporters:\n";
         IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
         IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
         std::size_t maxNameLen = 0;
@@ -4752,13 +4894,13 @@ namespace Catch {
                                                         .setInitialIndent( 0 )
                                                         .setIndent( 7+maxNameLen )
                                                         .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
-            std::cout << "  "
+            Catch::cout() << "  "
                     << it->first
                     << ":"
                     << std::string( maxNameLen - it->first.size() + 2, ' ' )
                     << wrapper << "\n";
         }
-        std::cout << std::endl;
+        Catch::cout() << std::endl;
         return factories.size();
     }
 
@@ -4808,32 +4950,15 @@ namespace SectionTracking {
 
         RunState runState() const { return m_runState; }
 
-        TrackedSection* findChild( std::string const& childName ) {
-            TrackedSections::iterator it = m_children.find( childName );
-            return it != m_children.end()
-                ? &it->second
-                : NULL;
-        }
-        TrackedSection* acquireChild( std::string const& childName ) {
-            if( TrackedSection* child = findChild( childName ) )
-                return child;
-            m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) );
-            return findChild( childName );
-        }
+        TrackedSection* findChild( std::string const& childName );
+        TrackedSection* acquireChild( std::string const& childName );
+
         void enter() {
             if( m_runState == NotStarted )
                 m_runState = Executing;
         }
-        void leave() {
-            for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end();
-                    it != itEnd;
-                    ++it )
-                if( it->second.runState() != Completed ) {
-                    m_runState = ExecutingChildren;
-                    return;
-                }
-            m_runState = Completed;
-        }
+        void leave();
+
         TrackedSection* getParent() {
             return m_parent;
         }
@@ -4846,9 +4971,31 @@ namespace SectionTracking {
         RunState m_runState;
         TrackedSections m_children;
         TrackedSection* m_parent;
-
     };
 
+    inline TrackedSection* TrackedSection::findChild( std::string const& childName ) {
+        TrackedSections::iterator it = m_children.find( childName );
+        return it != m_children.end()
+            ? &it->second
+            : NULL;
+    }
+    inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) {
+        if( TrackedSection* child = findChild( childName ) )
+            return child;
+        m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) );
+        return findChild( childName );
+    }
+    inline void TrackedSection::leave() {
+        for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end();
+                it != itEnd;
+                ++it )
+            if( it->second.runState() != Completed ) {
+                m_runState = ExecutingChildren;
+                return;
+            }
+        m_runState = Completed;
+    }
+
     class TestCaseTracker {
     public:
         TestCaseTracker( std::string const& testCaseName )
@@ -4915,6 +5062,81 @@ using SectionTracking::TestCaseTracker;
 
 } // namespace Catch
 
+// #included from: catch_fatal_condition.hpp
+#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
+
+namespace Catch {
+
+    // Report the error condition then exit the process
+    inline void fatal( std::string const& message, int exitCode ) {
+        IContext& context = Catch::getCurrentContext();
+        IResultCapture* resultCapture = context.getResultCapture();
+        resultCapture->handleFatalErrorCondition( message );
+
+		if( Catch::alwaysTrue() ) // avoids "no return" warnings
+            exit( exitCode );
+    }
+
+} // namespace Catch
+
+#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+		void reset() {}
+	};
+
+} // namespace Catch
+
+#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+
+#include <signal.h>
+
+namespace Catch {
+
+    struct SignalDefs { int id; const char* name; };
+    extern SignalDefs signalDefs[];
+    SignalDefs signalDefs[] = {
+            { SIGINT,  "SIGINT - Terminal interrupt signal" },
+            { SIGILL,  "SIGILL - Illegal instruction signal" },
+            { SIGFPE,  "SIGFPE - Floating point error signal" },
+            { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+            { SIGTERM, "SIGTERM - Termination request signal" },
+            { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+        };
+
+    struct FatalConditionHandler {
+
+        static void handleSignal( int sig ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                if( sig == signalDefs[i].id )
+                    fatal( signalDefs[i].name, -sig );
+            fatal( "<unknown signal>", -sig );
+        }
+
+        FatalConditionHandler() : m_isSet( true ) {
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                signal( signalDefs[i].id, handleSignal );
+        }
+        ~FatalConditionHandler() {
+            reset();
+        }
+        void reset() {
+            if( m_isSet ) {
+                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+                    signal( signalDefs[i].id, SIG_DFL );
+                m_isSet = false;
+            }
+        }
+
+        bool m_isSet;
+    };
+
+} // namespace Catch
+
+#endif // not Windows
+
 #include <set>
 #include <string>
 
@@ -5102,6 +5324,37 @@ namespace Catch {
             return &m_lastResult;
         }
 
+        virtual void handleFatalErrorCondition( std::string const& message ) {
+            ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
+            resultBuilder.setResultType( ResultWas::FatalErrorCondition );
+            resultBuilder << message;
+            resultBuilder.captureExpression();
+
+            handleUnfinishedSections();
+
+            // Recreate section for test case (as we will lose the one that was in scope)
+            TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+            SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+
+            Counts assertions;
+            assertions.failed = 1;
+            SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
+            m_reporter->sectionEnded( testCaseSectionStats );
+
+            TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
+
+            Totals deltaTotals;
+            deltaTotals.testCases.failed = 1;
+            m_reporter->testCaseEnded( TestCaseStats(   testInfo,
+                                                        deltaTotals,
+                                                        "",
+                                                        "",
+                                                        false ) );
+            m_totals.testCases.failed++;
+            testGroupEnded( "", m_totals, 1, 1 );
+            m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
+        }
+
     public:
         // !TBD We need to do this another way!
         bool aborting() const {
@@ -5123,12 +5376,12 @@ namespace Catch {
                 Timer timer;
                 timer.start();
                 if( m_reporter->getPreferences().shouldRedirectStdOut ) {
-                    StreamRedirect coutRedir( std::cout, redirectedCout );
-                    StreamRedirect cerrRedir( std::cerr, redirectedCerr );
-                    m_activeTestCase->invoke();
+                    StreamRedirect coutRedir( Catch::cout(), redirectedCout );
+                    StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+                    invokeActiveTestCase();
                 }
                 else {
-                    m_activeTestCase->invoke();
+                    invokeActiveTestCase();
                 }
                 duration = timer.getElapsedSeconds();
             }
@@ -5136,20 +5389,9 @@ namespace Catch {
                 // This just means the test was aborted due to failure
             }
             catch(...) {
-                ResultBuilder exResult( m_lastAssertionInfo.macroName.c_str(),
-                                        m_lastAssertionInfo.lineInfo,
-                                        m_lastAssertionInfo.capturedExpression.c_str(),
-                                        m_lastAssertionInfo.resultDisposition );
-                exResult.useActiveException();
+                makeUnexpectedResultBuilder().useActiveException();
             }
-            // If sections ended prematurely due to an exception we stored their
-            // infos here so we can tear them down outside the unwind process.
-            for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
-                        itEnd = m_unfinishedSections.rend();
-                    it != itEnd;
-                    ++it )
-                sectionEnded( it->info, it->prevAssertions, it->durationInSeconds );
-            m_unfinishedSections.clear();
+            handleUnfinishedSections();
             m_messages.clear();
 
             Counts assertions = m_totals.assertions - prevAssertions;
@@ -5165,7 +5407,32 @@ namespace Catch {
             m_reporter->sectionEnded( testCaseSectionStats );
         }
 
+        void invokeActiveTestCase() {
+            FatalConditionHandler fatalConditionHandler; // Handle signals
+            m_activeTestCase->invoke();
+            fatalConditionHandler.reset();
+        }
+
     private:
+
+        ResultBuilder makeUnexpectedResultBuilder() const {
+            return ResultBuilder(   m_lastAssertionInfo.macroName.c_str(),
+                                    m_lastAssertionInfo.lineInfo,
+                                    m_lastAssertionInfo.capturedExpression.c_str(),
+                                    m_lastAssertionInfo.resultDisposition );
+        }
+
+        void handleUnfinishedSections() {
+            // If sections ended prematurely due to an exception we stored their
+            // infos here so we can tear them down outside the unwind process.
+            for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
+                        itEnd = m_unfinishedSections.rend();
+                    it != itEnd;
+                    ++it )
+                sectionEnded( it->info, it->prevAssertions, it->durationInSeconds );
+            m_unfinishedSections.clear();
+        }
+
         struct UnfinishedSections {
             UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds )
             : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
@@ -5211,18 +5478,19 @@ namespace Catch {
     struct Version {
         Version(    unsigned int _majorVersion,
                     unsigned int _minorVersion,
-                    unsigned int _buildNumber,
-                    char const* const _branchName )
-        :   majorVersion( _majorVersion ),
-            minorVersion( _minorVersion ),
-            buildNumber( _buildNumber ),
-            branchName( _branchName )
-        {}
+                    unsigned int _patchNumber,
+                    std::string const& _branchName,
+                    unsigned int _buildNumber );
 
         unsigned int const majorVersion;
         unsigned int const minorVersion;
+        unsigned int const patchNumber;
+
+        // buildNumber is only used if branchName is not null
+        std::string const branchName;
         unsigned int const buildNumber;
-        char const* const branchName;
+
+        friend std::ostream& operator << ( std::ostream& os, Version const& version );
 
     private:
         void operator=( Version const& );
@@ -5253,7 +5521,7 @@ namespace Catch {
 
             Totals totals;
 
-            context.testGroupStarting( "", 1, 1 ); // deprecated?
+            context.testGroupStarting( "all tests", 1, 1 ); // deprecated?
 
             TestSpec testSpec = m_config->testSpec();
             if( !testSpec.hasFilters() )
@@ -5276,7 +5544,15 @@ namespace Catch {
                     m_testsAlreadyRun.insert( *it );
                 }
             }
-            context.testGroupEnded( "", totals, 1, 1 );
+            std::vector<TestCase> skippedTestCases;
+            getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true );
+
+            for( std::vector<TestCase>::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end();
+                    it != itEnd;
+                    ++it )
+                m_reporter->skipTest( *it );
+
+            context.testGroupEnded( "all tests", totals, 1, 1 );
             return totals;
         }
 
@@ -5313,7 +5589,7 @@ namespace Catch {
         std::set<TestCase> m_testsAlreadyRun;
     };
 
-    class Session {
+    class Session : NonCopyable {
         static bool alreadyInstantiated;
 
     public:
@@ -5324,7 +5600,7 @@ namespace Catch {
         : m_cli( makeCommandLineParser() ) {
             if( alreadyInstantiated ) {
                 std::string msg = "Only one instance of Catch::Session can ever be used";
-                std::cerr << msg << std::endl;
+                Catch::cerr() << msg << std::endl;
                 throw std::logic_error( msg );
             }
             alreadyInstantiated = true;
@@ -5334,15 +5610,10 @@ namespace Catch {
         }
 
         void showHelp( std::string const& processName ) {
-            std::cout << "\nCatch v"    << libraryVersion.majorVersion << "."
-                                        << libraryVersion.minorVersion << " build "
-                                        << libraryVersion.buildNumber;
-            if( libraryVersion.branchName != std::string( "master" ) )
-                std::cout << " (" << libraryVersion.branchName << " branch)";
-            std::cout << "\n";
+            Catch::cout() << "\nCatch v" << libraryVersion << "\n";
 
-            m_cli.usage( std::cout, processName );
-            std::cout << "For more detail usage please see the project docs\n" << std::endl;
+            m_cli.usage( Catch::cout(), processName );
+            Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
         }
 
         int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
@@ -5356,11 +5627,12 @@ namespace Catch {
             catch( std::exception& ex ) {
                 {
                     Colour colourGuard( Colour::Red );
-                    std::cerr   << "\nError(s) in input:\n"
-                                << Text( ex.what(), TextAttributes().setIndent(2) )
-                                << "\n\n";
+                    Catch::cerr()
+                        << "\nError(s) in input:\n"
+                        << Text( ex.what(), TextAttributes().setIndent(2) )
+                        << "\n\n";
                 }
-                m_cli.usage( std::cout, m_configData.processName );
+                m_cli.usage( Catch::cout(), m_configData.processName );
                 return (std::numeric_limits<int>::max)();
             }
             return 0;
@@ -5386,6 +5658,9 @@ namespace Catch {
             try
             {
                 config(); // Force config to be constructed
+
+                std::srand( m_configData.rngSeed );
+
                 Runner runner( m_config );
 
                 // Handle list request
@@ -5395,7 +5670,7 @@ namespace Catch {
                 return static_cast<int>( runner.runTests().assertions.failed );
             }
             catch( std::exception& ex ) {
-                std::cerr << ex.what() << std::endl;
+                Catch::cerr() << ex.what() << std::endl;
                 return (std::numeric_limits<int>::max)();
             }
         }
@@ -5436,10 +5711,18 @@ namespace Catch {
 #include <set>
 #include <sstream>
 #include <iostream>
+#include <algorithm>
 
 namespace Catch {
 
     class TestRegistry : public ITestCaseRegistry {
+        struct LexSort {
+            bool operator() (TestCase i,TestCase j) const { return (i<j);}
+        };
+        struct RandomNumberGenerator {
+            int operator()( int n ) const { return std::rand() % n; }
+        };
+
     public:
         TestRegistry() : m_unnamedCount( 0 ) {}
         virtual ~TestRegistry();
@@ -5462,7 +5745,7 @@ namespace Catch {
                 TestCase const& prev = *m_functions.find( testCase );
                 {
                     Colour colourGuard( Colour::Red );
-                    std::cerr   << "error: TEST_CASE( \"" << name << "\" ) already defined.\n"
+                    Catch::cerr()   << "error: TEST_CASE( \"" << name << "\" ) already defined.\n"
                                 << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n"
                                 << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl;
                 }
@@ -5478,18 +5761,38 @@ namespace Catch {
             return m_nonHiddenFunctions;
         }
 
-        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases ) const {
+        virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const {
+
             for( std::vector<TestCase>::const_iterator  it = m_functionsInOrder.begin(),
                                                         itEnd = m_functionsInOrder.end();
                     it != itEnd;
                     ++it ) {
-                if( testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ) )
+                bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() );
+                if( includeTest != negated )
                     matchingTestCases.push_back( *it );
             }
+            sortTests( config, matchingTestCases );
         }
 
     private:
 
+        static void sortTests( IConfig const& config, std::vector<TestCase>& matchingTestCases ) {
+
+            switch( config.runOrder() ) {
+                case RunTests::InLexicographicalOrder:
+                    std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() );
+                    break;
+                case RunTests::InRandomOrder:
+                {
+                    RandomNumberGenerator rng;
+                    std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng );
+                }
+                    break;
+                case RunTests::InDeclarationOrder:
+                    // already in declaration order
+                    break;
+            }
+        }
         std::set<TestCase> m_functions;
         std::vector<TestCase> m_functionsInOrder;
         std::vector<TestCase> m_nonHiddenFunctions;
@@ -5613,7 +5916,7 @@ namespace Catch {
                     throw;
                 }
                 @catch (NSException *exception) {
-                    return toString( [exception description] );
+                    return Catch::toString( [exception description] );
                 }
 #else
                 throw;
@@ -5760,6 +6063,7 @@ namespace Catch {
 
 #include <stdexcept>
 #include <cstdio>
+#include <iostream>
 
 namespace Catch {
 
@@ -5823,6 +6127,15 @@ namespace Catch {
             isOwned = false;
         }
     }
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions
+    std::ostream& cout() {
+        return std::cout;
+    }
+    std::ostream& cerr() {
+        return std::cerr;
+    }
+#endif
 }
 
 namespace Catch {
@@ -5872,7 +6185,7 @@ namespace Catch {
             std::string testName = getResultCapture()->getCurrentTestName();
 
             std::map<std::string, IGeneratorsForTest*>::const_iterator it =
-            m_generatorsByTestName.find( testName );
+                m_generatorsByTestName.find( testName );
             return it != m_generatorsByTestName.end()
                 ? it->second
                 : NULL;
@@ -5908,8 +6221,8 @@ namespace Catch {
     }
 
     Stream createStream( std::string const& streamName ) {
-        if( streamName == "stdout" ) return Stream( std::cout.rdbuf(), false );
-        if( streamName == "stderr" ) return Stream( std::cerr.rdbuf(), false );
+        if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false );
+        if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false );
         if( streamName == "debug" ) return Stream( new StreamBufImpl<OutputDebugWriter>, true );
 
         throw std::domain_error( "Unknown stream: " + streamName );
@@ -5924,14 +6237,35 @@ namespace Catch {
 // #included from: catch_console_colour_impl.hpp
 #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
 
-namespace Catch { namespace Detail {
-    struct IColourImpl {
-        virtual ~IColourImpl() {}
-        virtual void use( Colour::Code _colourCode ) = 0;
-    };
-}}
+namespace Catch {
+    namespace {
 
-#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+        struct IColourImpl {
+            virtual ~IColourImpl() {}
+            virtual void use( Colour::Code _colourCode ) = 0;
+        };
+
+        struct NoColourImpl : IColourImpl {
+            void use( Colour::Code ) {}
+
+            static IColourImpl* instance() {
+                static NoColourImpl s_instance;
+                return &s_instance;
+            }
+        };
+
+    } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+#   ifdef CATCH_PLATFORM_WINDOWS
+#       define CATCH_CONFIG_COLOUR_WINDOWS
+#   else
+#       define CATCH_CONFIG_COLOUR_ANSI
+#   endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
 
 #ifndef NOMINMAX
 #define NOMINMAX
@@ -5946,7 +6280,7 @@ namespace Catch { namespace Detail {
 namespace Catch {
 namespace {
 
-    class Win32ColourImpl : public Detail::IColourImpl {
+    class Win32ColourImpl : public IColourImpl {
     public:
         Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
         {
@@ -5983,11 +6317,7 @@ namespace {
         WORD originalAttributes;
     };
 
-    inline bool shouldUseColourForPlatform() {
-        return true;
-    }
-
-    static Detail::IColourImpl* platformColourInstance() {
+    IColourImpl* platformColourInstance() {
         static Win32ColourImpl s_instance;
         return &s_instance;
     }
@@ -5995,7 +6325,7 @@ namespace {
 } // end anon namespace
 } // end namespace Catch
 
-#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
 
 #include <unistd.h>
 
@@ -6006,7 +6336,7 @@ namespace {
     // Thanks to Adam Strzelecki for original contribution
     // (http://github.com/nanoant)
     // https://github.com/philsquared/Catch/pull/131
-    class PosixColourImpl : public Detail::IColourImpl {
+    class PosixColourImpl : public IColourImpl {
     public:
         virtual void use( Colour::Code _colourCode ) {
             switch( _colourCode ) {
@@ -6027,53 +6357,48 @@ namespace {
                 case Colour::Bright: throw std::logic_error( "not a colour" );
             }
         }
+        static IColourImpl* instance() {
+            static PosixColourImpl s_instance;
+            return &s_instance;
+        }
+
     private:
         void setColour( const char* _escapeCode ) {
-            std::cout << '\033' << _escapeCode;
+            Catch::cout() << '\033' << _escapeCode;
         }
     };
 
-    inline bool shouldUseColourForPlatform() {
-        return isatty(STDOUT_FILENO);
-    }
-
-    static Detail::IColourImpl* platformColourInstance() {
-        static PosixColourImpl s_instance;
-        return &s_instance;
+    IColourImpl* platformColourInstance() {
+        Ptr<IConfig const> config = getCurrentContext().getConfig();
+        return (config && config->forceColour()) || isatty(STDOUT_FILENO)
+            ? PosixColourImpl::instance()
+            : NoColourImpl::instance();
     }
 
 } // end anon namespace
 } // end namespace Catch
 
-#endif // not Windows
+#else  // not Windows or ANSI ///////////////////////////////////////////////
 
 namespace Catch {
 
-    namespace {
-        struct NoColourImpl : Detail::IColourImpl {
-            void use( Colour::Code ) {}
+    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
 
-            static IColourImpl* instance() {
-                static NoColourImpl s_instance;
-                return &s_instance;
-            }
-        };
-        static bool shouldUseColour() {
-            return shouldUseColourForPlatform() && !isDebuggerActive();
-        }
-    }
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
 
     Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
     Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
     Colour::~Colour(){ if( !m_moved ) use( None ); }
-    void Colour::use( Code _colourCode ) {
-        impl()->use( _colourCode );
-    }
 
-    Detail::IColourImpl* Colour::impl() {
-        return shouldUseColour()
-            ? platformColourInstance()
-            : NoColourImpl::instance();
+    void Colour::use( Code _colourCode ) {
+        static IColourImpl* impl = isDebuggerActive()
+            ? NoColourImpl::instance()
+            : platformColourInstance();
+        impl->use( _colourCode );
     }
 
 } // end namespace Catch
@@ -6238,7 +6563,7 @@ namespace Catch {
 namespace Catch {
 
     inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
-        if( tag == "." ||
+        if( startsWith( tag, "." ) ||
             tag == "hide" ||
             tag == "!hide" )
             return TestCaseInfo::IsHidden;
@@ -6258,13 +6583,13 @@ namespace Catch {
         if( isReservedTag( tag ) ) {
             {
                 Colour colourGuard( Colour::Red );
-                std::cerr
+                Catch::cerr()
                     << "Tag name [" << tag << "] not allowed.\n"
                     << "Tag names starting with non alpha-numeric characters are reserved\n";
             }
             {
                 Colour colourGuard( Colour::FileName );
-                std::cerr << _lineInfo << std::endl;
+                Catch::cerr() << _lineInfo << std::endl;
             }
             exit(1);
         }
@@ -6292,14 +6617,15 @@ namespace Catch {
             }
             else {
                 if( c == ']' ) {
-                    enforceNotReservedTag( tag, _lineInfo );
-
-                    inTag = false;
-                    if( tag == "hide" || tag == "." )
+                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+                    if( prop == TestCaseInfo::IsHidden )
                         isHidden = true;
-                    else
-                        tags.insert( tag );
+                    else if( prop == TestCaseInfo::None )
+                        enforceNotReservedTag( tag, _lineInfo );
+
+                    tags.insert( tag );
                     tag.clear();
+                    inTag = false;
                 }
                 else
                     tag += c;
@@ -6416,8 +6742,33 @@ namespace Catch {
 
 namespace Catch {
 
-    // These numbers are maintained by a script
-    Version libraryVersion( 1, 0, 53, "master" );
+    Version::Version
+        (   unsigned int _majorVersion,
+            unsigned int _minorVersion,
+            unsigned int _patchNumber,
+            std::string const& _branchName,
+            unsigned int _buildNumber )
+    :   majorVersion( _majorVersion ),
+        minorVersion( _minorVersion ),
+        patchNumber( _patchNumber ),
+        branchName( _branchName ),
+        buildNumber( _buildNumber )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, Version const& version ) {
+        os  << version.majorVersion << "."
+            << version.minorVersion << "."
+            << version.patchNumber;
+
+        if( !version.branchName.empty() ) {
+            os  << "-" << version.branchName
+                << "." << version.buildNumber;
+        }
+        return os;
+    }
+
+    Version libraryVersion( 1, 2, 1, "", 0 );
+
 }
 
 // #included from: catch_message.hpp
@@ -6501,6 +6852,7 @@ namespace Catch
         virtual void testCaseEnded( TestCaseStats const& testCaseStats );
         virtual void testGroupEnded( TestGroupStats const& testGroupStats );
         virtual void testRunEnded( TestRunStats const& testRunStats );
+        virtual void skipTest( TestCaseInfo const& );
 
     private:
         Ptr<IReporter> m_legacyReporter;
@@ -6574,6 +6926,8 @@ namespace Catch
     void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
         m_legacyReporter->EndTesting( testRunStats.totals );
     }
+    void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
+    }
 }
 
 // #included from: catch_timer.hpp
@@ -6596,11 +6950,11 @@ namespace Catch {
         uint64_t getCurrentTicks() {
             static uint64_t hz=0, hzo=0;
             if (!hz) {
-                QueryPerformanceFrequency((LARGE_INTEGER*)&hz);
-                QueryPerformanceCounter((LARGE_INTEGER*)&hzo);
+                QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
+                QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
             }
             uint64_t t;
-            QueryPerformanceCounter((LARGE_INTEGER*)&t);
+            QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
             return ((t-hzo)*1000000)/hz;
         }
 #else
@@ -6615,14 +6969,14 @@ namespace Catch {
     void Timer::start() {
         m_ticks = getCurrentTicks();
     }
-    unsigned int Timer::getElapsedNanoseconds() const {
+    unsigned int Timer::getElapsedMicroseconds() const {
         return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
     }
     unsigned int Timer::getElapsedMilliseconds() const {
-        return static_cast<unsigned int>((getCurrentTicks() - m_ticks)/1000);
+        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
     }
     double Timer::getElapsedSeconds() const {
-        return (getCurrentTicks() - m_ticks)/1000000.0;
+        return getElapsedMicroseconds()/1000000.0;
     }
 
 } // namespace Catch
@@ -6660,6 +7014,20 @@ namespace Catch {
         return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
     }
 
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+        bool replaced = false;
+        std::size_t i = str.find( replaceThis );
+        while( i != std::string::npos ) {
+            replaced = true;
+            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+            if( i < str.size()-withThis.size() )
+                i = str.find( replaceThis, i+withThis.size() );
+            else
+                i = std::string::npos;
+        }
+        return replaced;
+    }
+
     pluralise::pluralise( std::size_t count, std::string const& label )
     :   m_count( count ),
         m_label( label )
@@ -6687,6 +7055,9 @@ namespace Catch {
     bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
         return line == other.line && file == other.file;
     }
+    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
+        return line < other.line || ( line == other.line  && file < other.file );
+    }
 
     std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
 #ifndef __GNUG__
@@ -6781,7 +7152,7 @@ namespace Catch {
 
             size = sizeof(info);
             if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) {
-                std::cerr << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
                 return false;
             }
 
@@ -6822,7 +7193,7 @@ namespace Catch {
     namespace Catch {
         void writeToDebugConsole( std::string const& text ) {
             // !TBD: Need a version for Mac/ XCode and other IDEs
-            std::cout << text;
+            Catch::cout() << text;
         }
     }
 #endif // Platform
@@ -6834,6 +7205,8 @@ namespace Catch {
 
 namespace Detail {
 
+    std::string unprintableString = "{?}";
+
     namespace {
         struct Endianness {
             enum Arch { Big, Little };
@@ -6892,7 +7265,7 @@ std::string toString( std::wstring const& value ) {
     s.reserve( value.size() );
     for(size_t i = 0; i < value.size(); ++i )
         s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
-    return toString( s );
+    return Catch::toString( s );
 }
 
 std::string toString( const char* const value ) {
@@ -6916,20 +7289,21 @@ std::string toString( wchar_t* const value )
 std::string toString( int value ) {
     std::ostringstream oss;
     oss << value;
+    if( value >= 255 )
+        oss << " (0x" << std::hex << value << ")";
     return oss.str();
 }
 
 std::string toString( unsigned long value ) {
     std::ostringstream oss;
-    if( value > 8192 )
-        oss << "0x" << std::hex << value;
-    else
-        oss << value;
+    oss << value;
+    if( value >= 255 )
+        oss << " (0x" << std::hex << value << ")";
     return oss.str();
 }
 
 std::string toString( unsigned int value ) {
-    return toString( static_cast<unsigned long>( value ) );
+    return Catch::toString( static_cast<unsigned long>( value ) );
 }
 
 template<typename T>
@@ -7055,7 +7429,7 @@ namespace Catch {
         if( !result.isOk() ) {
             if( getCurrentContext().getConfig()->shouldDebugBreak() )
                 m_shouldDebugBreak = true;
-            if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal )
+            if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
                 m_shouldThrow = true;
         }
     }
@@ -7195,7 +7569,7 @@ namespace Catch {
         }
         catch( std::exception& ex ) {
             Colour colourGuard( Colour::Red );
-            std::cerr << ex.what() << std::endl;
+            Catch::cerr() << ex.what() << std::endl;
             exit(1);
         }
     }
@@ -7208,6 +7582,8 @@ namespace Catch {
 // #included from: catch_reporter_bases.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 
+#include <cstring>
+
 namespace Catch {
 
     struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
@@ -7240,7 +7616,6 @@ namespace Catch {
         }
         virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) {
             currentTestCaseInfo.reset();
-            assert( m_sectionStack.empty() );
         }
         virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) {
             currentGroupInfo.reset();
@@ -7251,6 +7626,11 @@ namespace Catch {
             currentTestRunInfo.reset();
         }
 
+        virtual void skipTest( TestCaseInfo const& ) {
+            // Don't do anything with this by default.
+            // It can optionally be overridden in the derived class.
+        }
+
         Ptr<IConfig> m_config;
         std::ostream& stream;
 
@@ -7380,6 +7760,8 @@ namespace Catch {
         }
         virtual void testRunEndedCumulative() = 0;
 
+        virtual void skipTest( TestCaseInfo const& ) {}
+
         Ptr<IConfig> m_config;
         std::ostream& stream;
         std::vector<AssertionStats> m_assertions;
@@ -7395,6 +7777,16 @@ namespace Catch {
 
     };
 
+    template<char C>
+    char const* getLineOfChars() {
+        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+        if( !*line ) {
+            memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+        }
+        return line;
+    }
+
 } // end namespace Catch
 
 // #included from: ../internal/catch_reporter_registrars.hpp
@@ -7464,7 +7856,6 @@ namespace Catch {
 #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 
 #include <sstream>
-#include <iostream>
 #include <string>
 #include <vector>
 
@@ -7507,7 +7898,7 @@ namespace Catch {
         XmlWriter()
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
-            m_os( &std::cout )
+            m_os( &Catch::cout() )
         {}
 
         XmlWriter( std::ostream& os )
@@ -7521,27 +7912,6 @@ namespace Catch {
                 endElement();
         }
 
-//#  ifndef CATCH_CPP11_OR_GREATER
-//        XmlWriter& operator = ( XmlWriter const& other ) {
-//            XmlWriter temp( other );
-//            swap( temp );
-//            return *this;
-//        }
-//#  else
-//        XmlWriter( XmlWriter const& )              = default;
-//        XmlWriter( XmlWriter && )                  = default;
-//        XmlWriter& operator = ( XmlWriter const& ) = default;
-//        XmlWriter& operator = ( XmlWriter && )     = default;
-//#  endif
-//
-//        void swap( XmlWriter& other ) {
-//            std::swap( m_tagIsOpen, other.m_tagIsOpen );
-//            std::swap( m_needsNewline, other.m_needsNewline );
-//            std::swap( m_tags, other.m_tags );
-//            std::swap( m_indent, other.m_indent );
-//            std::swap( m_os, other.m_os );
-//        }
-
         XmlWriter& startElement( std::string const& name ) {
             ensureTagClosed();
             newlineIfNecessary();
@@ -7677,81 +8047,90 @@ namespace Catch {
 
 }
 namespace Catch {
-    class XmlReporter : public SharedImpl<IReporter> {
+    class XmlReporter : public StreamingReporterBase {
     public:
-        XmlReporter( ReporterConfig const& config ) : m_config( config ), m_sectionDepth( 0 ) {}
+        XmlReporter( ReporterConfig const& _config )
+        :   StreamingReporterBase( _config ),
+            m_sectionDepth( 0 )
+        {}
+
+        virtual ~XmlReporter();
 
         static std::string getDescription() {
             return "Reports test results as an XML document";
         }
-        virtual ~XmlReporter();
 
-    private: // IReporter
-
-        virtual bool shouldRedirectStdout() const {
-            return true;
+    public: // StreamingReporterBase
+        virtual ReporterPreferences getPreferences() const {
+            ReporterPreferences prefs;
+            prefs.shouldRedirectStdOut = true;
+            return prefs;
         }
 
-        virtual void StartTesting() {
-            m_xml.setStream( m_config.stream() );
-            m_xml.startElement( "Catch" );
-            if( !m_config.fullConfig()->name().empty() )
-                m_xml.writeAttribute( "name", m_config.fullConfig()->name() );
+        virtual void noMatchingTestCases( std::string const& s ) {
+            StreamingReporterBase::noMatchingTestCases( s );
         }
 
-        virtual void EndTesting( const Totals& totals ) {
-            m_xml.scopedElement( "OverallResults" )
-                .writeAttribute( "successes", totals.assertions.passed )
-                .writeAttribute( "failures", totals.assertions.failed )
-                .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
-            m_xml.endElement();
+        virtual void testRunStarting( TestRunInfo const& testInfo ) {
+            StreamingReporterBase::testRunStarting( testInfo );
+            m_xml.setStream( stream );
+            m_xml.startElement( "Catch" );
+            if( !m_config->name().empty() )
+                m_xml.writeAttribute( "name", m_config->name() );
         }
 
-        virtual void StartGroup( const std::string& groupName ) {
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) {
+            StreamingReporterBase::testGroupStarting( groupInfo );
             m_xml.startElement( "Group" )
-                .writeAttribute( "name", groupName );
+                .writeAttribute( "name", groupInfo.name );
         }
 
-        virtual void EndGroup( const std::string&, const Totals& totals ) {
-            m_xml.scopedElement( "OverallResults" )
-                .writeAttribute( "successes", totals.assertions.passed )
-                .writeAttribute( "failures", totals.assertions.failed )
-                .writeAttribute( "expectedFailures", totals.assertions.failedButOk );
-            m_xml.endElement();
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) {
+            StreamingReporterBase::testCaseStarting(testInfo);
+            m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                m_testCaseTimer.start();
         }
 
-        virtual void StartSection( const std::string& sectionName, const std::string& description ) {
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) {
+            StreamingReporterBase::sectionStarting( sectionInfo );
             if( m_sectionDepth++ > 0 ) {
                 m_xml.startElement( "Section" )
-                    .writeAttribute( "name", trim( sectionName ) )
-                    .writeAttribute( "description", description );
+                    .writeAttribute( "name", trim( sectionInfo.name ) )
+                    .writeAttribute( "description", sectionInfo.description );
             }
         }
-        virtual void NoAssertionsInSection( const std::string& ) {}
-        virtual void NoAssertionsInTestCase( const std::string& ) {}
 
-        virtual void EndSection( const std::string& /*sectionName*/, const Counts& assertions ) {
-            if( --m_sectionDepth > 0 ) {
-                m_xml.scopedElement( "OverallResults" )
-                    .writeAttribute( "successes", assertions.passed )
-                    .writeAttribute( "failures", assertions.failed )
-                    .writeAttribute( "expectedFailures", assertions.failedButOk );
-                m_xml.endElement();
-            }
-        }
+        virtual void assertionStarting( AssertionInfo const& ) { }
 
-        virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) {
-            m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
-            m_currentTestSuccess = true;
-        }
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+            const AssertionResult& assertionResult = assertionStats.assertionResult;
 
-        virtual void Result( const Catch::AssertionResult& assertionResult ) {
-            if( !m_config.fullConfig()->includeSuccessfulResults() && assertionResult.getResultType() == ResultWas::Ok )
-                return;
+            // Print any info messages in <Info> tags.
+            if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+                for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+                        it != itEnd;
+                        ++it ) {
+                    if( it->type == ResultWas::Info ) {
+                        m_xml.scopedElement( "Info" )
+                            .writeText( it->message );
+                    } else if ( it->type == ResultWas::Warning ) {
+                        m_xml.scopedElement( "Warning" )
+                            .writeText( it->message );
+                    }
+                }
+            }
 
+            // Drop out if result was successful but we're not printing them.
+            if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+                return true;
+
+            // Print the expression if there is one.
             if( assertionResult.hasExpression() ) {
                 m_xml.startElement( "Expression" )
                     .writeAttribute( "success", assertionResult.succeeded() )
+					.writeAttribute( "type", assertionResult.getTestMacroName() )
                     .writeAttribute( "filename", assertionResult.getSourceInfo().file )
                     .writeAttribute( "line", assertionResult.getSourceInfo().line );
 
@@ -7759,58 +8138,96 @@ namespace Catch {
                     .writeText( assertionResult.getExpression() );
                 m_xml.scopedElement( "Expanded" )
                     .writeText( assertionResult.getExpandedExpression() );
-                m_currentTestSuccess &= assertionResult.succeeded();
             }
 
+            // And... Print a result applicable to each result type.
             switch( assertionResult.getResultType() ) {
                 case ResultWas::ThrewException:
                     m_xml.scopedElement( "Exception" )
                         .writeAttribute( "filename", assertionResult.getSourceInfo().file )
                         .writeAttribute( "line", assertionResult.getSourceInfo().line )
                         .writeText( assertionResult.getMessage() );
-                    m_currentTestSuccess = false;
+                    break;
+                case ResultWas::FatalErrorCondition:
+                    m_xml.scopedElement( "Fatal Error Condition" )
+                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
+                        .writeText( assertionResult.getMessage() );
                     break;
                 case ResultWas::Info:
                     m_xml.scopedElement( "Info" )
                         .writeText( assertionResult.getMessage() );
                     break;
                 case ResultWas::Warning:
-                    m_xml.scopedElement( "Warning" )
-                        .writeText( assertionResult.getMessage() );
+                    // Warning will already have been written
                     break;
                 case ResultWas::ExplicitFailure:
                     m_xml.scopedElement( "Failure" )
                         .writeText( assertionResult.getMessage() );
-                    m_currentTestSuccess = false;
                     break;
-                case ResultWas::Unknown:
-                case ResultWas::Ok:
-                case ResultWas::FailureBit:
-                case ResultWas::ExpressionFailed:
-                case ResultWas::Exception:
-                case ResultWas::DidntThrowException:
+                default:
                     break;
             }
+
             if( assertionResult.hasExpression() )
                 m_xml.endElement();
+
+            return true;
         }
 
-        virtual void Aborted() {
-            // !TBD
+        virtual void sectionEnded( SectionStats const& sectionStats ) {
+            StreamingReporterBase::sectionEnded( sectionStats );
+            if( --m_sectionDepth > 0 ) {
+                XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+                e.writeAttribute( "successes", sectionStats.assertions.passed );
+                e.writeAttribute( "failures", sectionStats.assertions.failed );
+                e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+                if ( m_config->showDurations() == ShowDurations::Always )
+                    e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+                m_xml.endElement();
+            }
         }
 
-        virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string&, const std::string& ) {
-            m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess );
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+            StreamingReporterBase::testCaseEnded( testCaseStats );
+            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+            e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+            m_xml.endElement();
+        }
+
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+            StreamingReporterBase::testGroupEnded( testGroupStats );
+            // TODO: Check testGroupStats.aborting and act accordingly.
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+                .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+            m_xml.endElement();
+        }
+
+        virtual void testRunEnded( TestRunStats const& testRunStats ) {
+            StreamingReporterBase::testRunEnded( testRunStats );
+            m_xml.scopedElement( "OverallResults" )
+                .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+                .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+                .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
             m_xml.endElement();
         }
 
     private:
-        ReporterConfig m_config;
-        bool m_currentTestSuccess;
+        Timer m_testCaseTimer;
         XmlWriter m_xml;
         int m_sectionDepth;
     };
 
+     INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
 } // end namespace Catch
 
 // #included from: ../reporters/catch_reporter_junit.hpp
@@ -7937,7 +8354,7 @@ namespace Catch {
                     xml.writeAttribute( "classname", className );
                     xml.writeAttribute( "name", name );
                 }
-                xml.writeAttribute( "time", toString( sectionNode.stats.durationInSeconds ) );
+                xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
 
                 writeAssertions( sectionNode );
 
@@ -7970,6 +8387,7 @@ namespace Catch {
                 std::string elementName;
                 switch( result.getResultType() ) {
                     case ResultWas::ThrewException:
+                    case ResultWas::FatalErrorCondition:
                         elementName = "error";
                         break;
                     case ResultWas::ExplicitFailure:
@@ -8028,8 +8446,6 @@ namespace Catch {
 // #included from: ../reporters/catch_reporter_console.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
 
-#include <cstring>
-
 namespace Catch {
 
     struct ConsoleReporter : StreamingReporterBase {
@@ -8164,6 +8580,11 @@ namespace Catch {
                         passOrFail = "FAILED";
                         messageLabel = "due to unexpected exception with message";
                         break;
+                    case ResultWas::FatalErrorCondition:
+                        colour = Colour::Error;
+                        passOrFail = "FAILED";
+                        messageLabel = "due to a fatal error condition";
+                        break;
                     case ResultWas::DidntThrowException:
                         colour = Colour::Error;
                         passOrFail = "FAILED";
@@ -8273,14 +8694,12 @@ namespace Catch {
             stream  << "\n" << getLineOfChars<'~'>() << "\n";
             Colour colour( Colour::SecondaryText );
             stream  << currentTestRunInfo->name
-                    << " is a Catch v"  << libraryVersion.majorVersion << "."
-                    << libraryVersion.minorVersion << " b"
-                    << libraryVersion.buildNumber;
-            if( libraryVersion.branchName != std::string( "master" ) )
-                stream << " (" << libraryVersion.branchName << ")";
-            stream  << " host application.\n"
+                    << " is a Catch v"  << libraryVersion << " host application.\n"
                     << "Run with -? for options\n\n";
 
+            if( m_config->rngSeed() != 0 )
+                stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
             currentTestRunInfo.used = true;
         }
         void lazyPrintGroupInfo() {
@@ -8452,15 +8871,6 @@ namespace Catch {
         void printSummaryDivider() {
             stream << getLineOfChars<'-'>() << "\n";
         }
-        template<char C>
-        static char const* getLineOfChars() {
-            static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
-            if( !*line ) {
-                memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
-                line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
-            }
-            return line;
-        }
 
     private:
         bool m_headerPrinted;
@@ -8569,6 +8979,13 @@ namespace Catch {
                         printExpressionWas();
                         printRemainingMessages();
                         break;
+                    case ResultWas::FatalErrorCondition:
+                        printResultType( Colour::Error, failedString() );
+                        printIssue( "fatal error condition with message:" );
+                        printMessage();
+                        printExpressionWas();
+                        printRemainingMessages();
+                        break;
                     case ResultWas::DidntThrowException:
                         printResultType( Colour::Error, failedString() );
                         printIssue( "expected exception, got none" );
@@ -8798,8 +9215,6 @@ namespace Catch {
     Matchers::Impl::StdString::EndsWith::~EndsWith() {}
 
     void Config::dummy() {}
-
-    INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter )
 }
 
 #ifdef __clang__
@@ -8988,9 +9403,13 @@ using Catch::Detail::Approx;
 #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
 
 #ifdef __clang__
-#pragma clang diagnostic pop
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
 #elif defined __GNUC__
-#pragma GCC diagnostic pop
+#    pragma GCC diagnostic pop
 #endif
 
 #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
diff --git a/test/t/basic/helper.hpp b/test/t/basic/helper.hpp
index 61e0769..5a2130e 100644
--- a/test/t/basic/helper.hpp
+++ b/test/t/basic/helper.hpp
@@ -9,7 +9,7 @@
 
 inline void add_tags(osmium::memory::Buffer& buffer, osmium::builder::Builder& builder, const std::vector<std::pair<const char*, const char*>>& tags) {
     osmium::builder::TagListBuilder tl_builder(buffer, &builder);
-    for (auto& tag : tags) {
+    for (const auto& tag : tags) {
         tl_builder.add_tag(tag.first, tag.second);
     }
 }
@@ -26,9 +26,11 @@ inline osmium::Way& buffer_add_way(osmium::memory::Buffer& buffer, const char* u
     osmium::builder::WayBuilder builder(buffer);
     builder.add_user(user);
     add_tags(buffer, builder, tags);
-    osmium::builder::WayNodeListBuilder wnl_builder(buffer, &builder);
-    for (const osmium::object_id_type ref : nodes) {
-        wnl_builder.add_node_ref(ref);
+    {
+        osmium::builder::WayNodeListBuilder wnl_builder(buffer, &builder);
+        for (const osmium::object_id_type ref : nodes) {
+            wnl_builder.add_node_ref(ref);
+        }
     }
     buffer.commit();
     return builder.object();
@@ -38,9 +40,11 @@ inline osmium::Way& buffer_add_way(osmium::memory::Buffer& buffer, const char* u
     osmium::builder::WayBuilder builder(buffer);
     builder.add_user(user);
     add_tags(buffer, builder, tags);
-    osmium::builder::WayNodeListBuilder wnl_builder(buffer, &builder);
-    for (auto& p : nodes) {
-        wnl_builder.add_node_ref(p.first, p.second);
+    {
+        osmium::builder::WayNodeListBuilder wnl_builder(buffer, &builder);
+        for (const auto& p : nodes) {
+            wnl_builder.add_node_ref(p.first, p.second);
+        }
     }
     buffer.commit();
     return builder.object();
@@ -53,9 +57,11 @@ inline osmium::Relation& buffer_add_relation(
     osmium::builder::RelationBuilder builder(buffer);
     builder.add_user(user);
     add_tags(buffer, builder, tags);
-    osmium::builder::RelationMemberListBuilder rml_builder(buffer, &builder);
-    for (const auto& member : members) {
-        rml_builder.add_member(osmium::char_to_item_type(std::get<0>(member)), std::get<1>(member), std::get<2>(member));
+    {
+        osmium::builder::RelationMemberListBuilder rml_builder(buffer, &builder);
+        for (const auto& member : members) {
+            rml_builder.add_member(osmium::char_to_item_type(std::get<0>(member)), std::get<1>(member), std::get<2>(member));
+        }
     }
     buffer.commit();
     return builder.object();
@@ -69,15 +75,15 @@ inline osmium::Area& buffer_add_area(osmium::memory::Buffer& buffer, const char*
     builder.add_user(user);
     add_tags(buffer, builder, tags);
 
-    for (auto& ring : rings) {
+    for (const auto& ring : rings) {
         if (ring.first) {
             osmium::builder::OuterRingBuilder ring_builder(buffer, &builder);
-            for (auto& p : ring.second) {
+            for (const auto& p : ring.second) {
                 ring_builder.add_node_ref(p.first, p.second);
             }
         } else {
             osmium::builder::InnerRingBuilder ring_builder(buffer, &builder);
-            for (auto& p : ring.second) {
+            for (const auto& p : ring.second) {
                 ring_builder.add_node_ref(p.first, p.second);
             }
         }
diff --git a/test/t/basic/test_box.cpp b/test/t/basic/test_box.cpp
index 8182fbf..768cf41 100644
--- a/test/t/basic/test_box.cpp
+++ b/test/t/basic/test_box.cpp
@@ -2,7 +2,10 @@
 
 #include <sstream>
 
+#include <boost/crc.hpp>
+
 #include <osmium/osm/box.hpp>
+#include <osmium/osm/crc.hpp>
 #include <osmium/geom/relations.hpp>
 
 TEST_CASE("Box") {
@@ -48,6 +51,10 @@ TEST_CASE("Box") {
         REQUIRE(b.contains(loc1));
         REQUIRE(b.contains(loc2));
         REQUIRE(b.contains(loc3));
+
+        osmium::CRC<boost::crc_32_type> crc32;
+        crc32.update(b);
+        REQUIRE(crc32().checksum() == 0xd381a838);
     }
 
     SECTION("output_defined") {
diff --git a/test/t/basic/test_changeset.cpp b/test/t/basic/test_changeset.cpp
index 2549c1e..fc9f1bd 100644
--- a/test/t/basic/test_changeset.cpp
+++ b/test/t/basic/test_changeset.cpp
@@ -1,12 +1,16 @@
 #include "catch.hpp"
 
+#include <boost/crc.hpp>
+
 #include <osmium/osm/changeset.hpp>
+#include <osmium/osm/crc.hpp>
 
 #include "helper.hpp"
 
-TEST_CASE("Basic_Changeset") {
+TEST_CASE("Basic Changeset") {
+
+    osmium::CRC<boost::crc_32_type> crc32;
 
-SECTION("changeset_builder") {
     osmium::memory::Buffer buffer(10 * 1000);
 
     osmium::Changeset& cs1 = buffer_add_changeset(buffer,
@@ -28,6 +32,9 @@ SECTION("changeset_builder") {
     REQUIRE(1 == cs1.tags().size());
     REQUIRE(std::string("user") == cs1.user());
 
+    crc32.update(cs1);
+    REQUIRE(crc32().checksum() == 0xf44aff25);
+
     osmium::Changeset& cs2 = buffer_add_changeset(buffer,
         "user",
         {{"comment", "foo"}, {"foo", "bar"}});
@@ -52,6 +59,5 @@ SECTION("changeset_builder") {
     REQUIRE(cs1 <= cs2);
     REQUIRE(false == (cs1 > cs2));
     REQUIRE(false == (cs1 >= cs2));
-}
 
 }
diff --git a/test/t/basic/test_crc.cpp b/test/t/basic/test_crc.cpp
new file mode 100644
index 0000000..aab1013
--- /dev/null
+++ b/test/t/basic/test_crc.cpp
@@ -0,0 +1,49 @@
+#include "catch.hpp"
+
+#include <boost/crc.hpp>
+
+#include <osmium/osm/crc.hpp>
+
+#include "helper.hpp"
+
+TEST_CASE("CRC of basic datatypes") {
+
+    osmium::CRC<boost::crc_32_type> crc32;
+
+    SECTION("Bool") {
+        crc32.update_bool(true);
+        crc32.update_bool(false);
+
+        REQUIRE(crc32().checksum() == 0x58c223be);
+    }
+
+    SECTION("Char") {
+        crc32.update_int8('x');
+        crc32.update_int8('y');
+
+        REQUIRE(crc32().checksum() == 0x8fe62899);
+    }
+
+    SECTION("String") {
+        const char* str = "foobar";
+        crc32.update_string(str);
+
+        REQUIRE(crc32().checksum() == 0x9ef61f95);
+    }
+
+    SECTION("Timestamp") {
+        osmium::Timestamp t("2015-07-12T13:10:46Z");
+        crc32.update(t);
+
+        REQUIRE(crc32().checksum() == 0x58a29d7);
+    }
+
+    SECTION("Location") {
+        osmium::Location loc { 3.46, 2.001 };
+        crc32.update(loc);
+
+        REQUIRE(crc32().checksum() == 0xddee042c);
+    }
+
+}
+
diff --git a/test/t/basic/test_node.cpp b/test/t/basic/test_node.cpp
index 6c2c899..db5b4cd 100644
--- a/test/t/basic/test_node.cpp
+++ b/test/t/basic/test_node.cpp
@@ -1,11 +1,16 @@
 #include "catch.hpp"
 
+#include <boost/crc.hpp>
+
+#include <osmium/osm/crc.hpp>
 #include <osmium/osm/node.hpp>
 
 #include "helper.hpp"
 
 TEST_CASE("Basic_Node") {
 
+    osmium::CRC<boost::crc_32_type> crc32;
+
 SECTION("node_builder") {
     osmium::memory::Buffer buffer(10000);
 
@@ -36,6 +41,9 @@ SECTION("node_builder") {
     REQUIRE(osmium::Location(3.5, 4.7) == node.location());
     REQUIRE(2 == node.tags().size());
 
+    crc32.update(node);
+    REQUIRE(crc32().checksum() == 0xc696802f);
+
     node.set_visible(false);
     REQUIRE(false == node.visible());
     REQUIRE(true == node.deleted());
diff --git a/test/t/basic/test_relation.cpp b/test/t/basic/test_relation.cpp
index 4c62a41..fd5c7b4 100644
--- a/test/t/basic/test_relation.cpp
+++ b/test/t/basic/test_relation.cpp
@@ -1,12 +1,16 @@
 #include "catch.hpp"
 
+#include <boost/crc.hpp>
+
+#include <osmium/osm/crc.hpp>
 #include <osmium/osm/relation.hpp>
 
 #include "helper.hpp"
 
-TEST_CASE("Basic_Relation") {
+TEST_CASE("Build relation") {
+
+    osmium::CRC<boost::crc_32_type> crc32;
 
-SECTION("relation_builder") {
     osmium::memory::Buffer buffer(10000);
 
     osmium::Relation& relation = buffer_add_relation(buffer,
@@ -55,6 +59,7 @@ SECTION("relation_builder") {
         }
         ++n;
     }
-}
 
+    crc32.update(relation);
+    REQUIRE(crc32().checksum() == 0xebcd836d);
 }
diff --git a/test/t/basic/test_way.cpp b/test/t/basic/test_way.cpp
index 9d2ba06..7c7bc21 100644
--- a/test/t/basic/test_way.cpp
+++ b/test/t/basic/test_way.cpp
@@ -1,11 +1,16 @@
 #include "catch.hpp"
 
+#include <boost/crc.hpp>
+
 #include <osmium/builder/osm_object_builder.hpp>
+#include <osmium/osm/crc.hpp>
 #include <osmium/osm/way.hpp>
 
 #include "helper.hpp"
 
-TEST_CASE("Basic_Way") {
+TEST_CASE("Build way") {
+
+    osmium::CRC<boost::crc_32_type> crc32;
 
 SECTION("way_builder") {
     osmium::memory::Buffer buffer(10000);
@@ -38,6 +43,9 @@ SECTION("way_builder") {
     REQUIRE(3 == way.nodes()[1].ref());
     REQUIRE(2 == way.nodes()[2].ref());
     REQUIRE(! way.is_closed());
+
+    crc32.update(way);
+    REQUIRE(crc32().checksum() == 0x20fe7a30);
 }
 
 SECTION("closed_way") {
diff --git a/test/t/geom/test_geos.cpp b/test/t/geom/test_geos.cpp
index e93228b..d849e3b 100644
--- a/test/t/geom/test_geos.cpp
+++ b/test/t/geom/test_geos.cpp
@@ -5,9 +5,7 @@
 
 #include "../basic/helper.hpp"
 
-TEST_CASE("GEOS_Geometry") {
-
-SECTION("point") {
+TEST_CASE("GEOS geometry factory - create point") {
     osmium::geom::GEOSFactory<> factory;
 
     std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
@@ -16,7 +14,7 @@ SECTION("point") {
     REQUIRE(-1 == point->getSRID());
 }
 
-SECTION("non_default_srid") {
+TEST_CASE("GEOS geometry factory - create point with non-default srid") {
     osmium::geom::GEOSFactory<> factory(4326);
 
     std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
@@ -25,13 +23,23 @@ SECTION("non_default_srid") {
     REQUIRE(4326 == point->getSRID());
 }
 
-SECTION("empty_point") {
+TEST_CASE("GEOS geometry factory - create point with externally created GEOS factory") {
+    geos::geom::GeometryFactory geos_factory;
+    osmium::geom::GEOSFactory<> factory(geos_factory);
+
+    std::unique_ptr<geos::geom::Point> point {factory.create_point(osmium::Location(3.2, 4.2))};
+    REQUIRE(3.2 == point->getX());
+    REQUIRE(4.2 == point->getY());
+    REQUIRE(0 == point->getSRID());
+}
+
+TEST_CASE("GEOS geometry factory - can not create from invalid location") {
     osmium::geom::GEOSFactory<> factory;
 
     REQUIRE_THROWS_AS(factory.create_point(osmium::Location()), osmium::invalid_location);
 }
 
-SECTION("linestring") {
+TEST_CASE("GEOS geometry factory - create linestring") {
     osmium::geom::GEOSFactory<> factory;
 
     osmium::memory::Buffer buffer(10000);
@@ -42,7 +50,7 @@ SECTION("linestring") {
         {2, {3.6, 4.9}}
     });
 
-    {
+    SECTION("from way node list") {
         std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl)};
         REQUIRE(3 == linestring->getNumPoints());
 
@@ -52,7 +60,7 @@ SECTION("linestring") {
         REQUIRE(3.6 == p2->getX());
     }
 
-    {
+    SECTION("without duplicates and backwards") {
         std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::unique, osmium::geom::direction::backward)};
         REQUIRE(3 == linestring->getNumPoints());
         std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
@@ -61,14 +69,14 @@ SECTION("linestring") {
         REQUIRE(3.2 == p2->getX());
     }
 
-    {
+    SECTION("with duplicates") {
         std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all)};
         REQUIRE(4 == linestring->getNumPoints());
         std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
         REQUIRE(3.2 == p0->getX());
     }
 
-    {
+    SECTION("with duplicates and backwards") {
         std::unique_ptr<geos::geom::LineString> linestring {factory.create_linestring(wnl, osmium::geom::use_nodes::all, osmium::geom::direction::backward)};
         REQUIRE(4 == linestring->getNumPoints());
         std::unique_ptr<geos::geom::Point> p0 = std::unique_ptr<geos::geom::Point>(linestring->getPointN(0));
@@ -76,7 +84,7 @@ SECTION("linestring") {
     }
 }
 
-SECTION("area_1outer_0inner") {
+TEST_CASE("GEOS geometry factory - create area with one outer and no inner rings") {
     osmium::geom::GEOSFactory<> factory;
 
     osmium::memory::Buffer buffer(10000);
@@ -105,7 +113,7 @@ SECTION("area_1outer_0inner") {
     REQUIRE(3.5 == l0e_p0->getX());
 }
 
-SECTION("area_1outer_1inner") {
+TEST_CASE("GEOS geometry factory - create area with one outer and one inner ring") {
     osmium::geom::GEOSFactory<> factory;
 
     osmium::memory::Buffer buffer(10000);
@@ -142,7 +150,7 @@ SECTION("area_1outer_1inner") {
     REQUIRE(5 == l0i0->getNumPoints());
 }
 
-SECTION("area_2outer_2inner") {
+TEST_CASE("GEOS geometry factory - create area with two outer and two inner rings") {
     osmium::geom::GEOSFactory<> factory;
 
     osmium::memory::Buffer buffer(10000);
@@ -195,4 +203,3 @@ SECTION("area_2outer_2inner") {
     REQUIRE(5 == l1e->getNumPoints());
 }
 
-}
diff --git a/test/t/io/test_file_formats.cpp b/test/t/io/test_file_formats.cpp
index e8785d6..f0ba0c6 100644
--- a/test/t/io/test_file_formats.cpp
+++ b/test/t/io/test_file_formats.cpp
@@ -247,5 +247,29 @@ TEST_CASE("FileFormats") {
         REQUIRE_THROWS_AS(f.check(), std::runtime_error);
     }
 
+    SECTION("url without format") {
+        osmium::io::File f {"http://www.example.com/api"};
+        REQUIRE(osmium::io::file_format::xml == f.format());
+        REQUIRE(osmium::io::file_compression::none == f.compression());
+        REQUIRE(false == f.has_multiple_object_versions());
+        f.check();
+    }
+
+    SECTION("url without format and filename") {
+        osmium::io::File f {"http://planet.osm.org/pbf/planet-latest.osm.pbf"};
+        REQUIRE(osmium::io::file_format::pbf == f.format());
+        REQUIRE(osmium::io::file_compression::none == f.compression());
+        REQUIRE(false == f.has_multiple_object_versions());
+        f.check();
+    }
+
+    SECTION("url with format") {
+        osmium::io::File f {"http://www.example.com/api", "osh"};
+        REQUIRE(osmium::io::file_format::xml == f.format());
+        REQUIRE(osmium::io::file_compression::none == f.compression());
+        REQUIRE(true == f.has_multiple_object_versions());
+        f.check();
+    }
+
 }
 
diff --git a/test/t/io/test_string_table.cpp b/test/t/io/test_string_table.cpp
new file mode 100644
index 0000000..7fedfcf
--- /dev/null
+++ b/test/t/io/test_string_table.cpp
@@ -0,0 +1,94 @@
+#include "catch.hpp"
+
+#include <osmium/io/detail/string_table.hpp>
+
+TEST_CASE("String store") {
+    osmium::io::detail::StringStore ss(100);
+
+    SECTION("empty") {
+        REQUIRE(ss.begin() == ss.end());
+    }
+
+    SECTION("add zero-length string") {
+        const char* s1 = ss.add("");
+        REQUIRE(std::string(s1) == "");
+
+        auto it = ss.begin();
+        REQUIRE(s1 == *it);
+        REQUIRE(std::string(*it) == "");
+        REQUIRE(++it == ss.end());
+    }
+
+    SECTION("add strings") {
+        const char* s1 = ss.add("foo");
+        const char* s2 = ss.add("bar");
+        REQUIRE(s1 != s2);
+        REQUIRE(std::string(s1) == "foo");
+        REQUIRE(std::string(s2) == "bar");
+
+        auto it = ss.begin();
+        REQUIRE(s1 == *it++);
+        REQUIRE(s2 == *it++);
+        REQUIRE(it == ss.end());
+    }
+
+    SECTION("add zero-length string and longer strings") {
+        const char* s1 = ss.add("");
+        const char* s2 = ss.add("xxx");
+        const char* s3 = ss.add("yyyyy");
+
+        auto it = ss.begin();
+        REQUIRE(std::string(*it++) == "");
+        REQUIRE(std::string(*it++) == "xxx");
+        REQUIRE(std::string(*it++) == "yyyyy");
+        REQUIRE(it == ss.end());
+    }
+
+    SECTION("add many strings") {
+        for (const char* teststring : {"a", "abc", "abcd", "abcde"}) {
+            int i = 0;
+            for (; i < 100; ++i) {
+                ss.add(teststring);
+            }
+
+            for (const char* s : ss) {
+                REQUIRE(std::string(s) == teststring);
+                --i;
+            }
+
+            REQUIRE(i == 0);
+            ss.clear();
+        }
+    }
+
+}
+
+TEST_CASE("String table") {
+    osmium::io::detail::StringTable st;
+
+    SECTION("empty") {
+        REQUIRE(st.size() == 1);
+        REQUIRE(std::next(st.begin()) == st.end());
+    }
+
+    SECTION("add strings") {
+        REQUIRE(st.add("foo") == 1);
+        REQUIRE(st.add("bar") == 2);
+        REQUIRE(st.add("bar") == 2);
+        REQUIRE(st.add("baz") == 3);
+        REQUIRE(st.add("foo") == 1);
+        REQUIRE(st.size() == 4);
+
+        auto it = st.begin();
+        REQUIRE(std::string("") == *it++);
+        REQUIRE(std::string("foo") == *it++);
+        REQUIRE(std::string("bar") == *it++);
+        REQUIRE(std::string("baz") == *it++);
+        REQUIRE(it == st.end());
+
+        st.clear();
+        REQUIRE(st.size() == 1);
+    }
+
+}
+
diff --git a/test/t/tags/test_tag_list.cpp b/test/t/tags/test_tag_list.cpp
index c2512d1..77523e7 100644
--- a/test/t/tags/test_tag_list.cpp
+++ b/test/t/tags/test_tag_list.cpp
@@ -4,73 +4,99 @@
 #include <osmium/memory/buffer.hpp>
 #include <osmium/osm/tag.hpp>
 
-TEST_CASE("tag_list") {
+TEST_CASE("create tag list") {
+    osmium::memory::Buffer buffer(10240);
+
+    SECTION("with TagListBuilder from char*") {
+        {
+            osmium::builder::TagListBuilder builder(buffer);
+            builder.add_tag("highway", "primary");
+            builder.add_tag("name", "Main Street");
+        }
+        buffer.commit();
+    }
 
-    SECTION("can_be_created_from_initializer_list") {
-        osmium::memory::Buffer buffer(10240);
+    SECTION("with TagListBuilder from char* with length") {
+        {
+            osmium::builder::TagListBuilder builder(buffer);
+            builder.add_tag("highway", strlen("highway"), "primary", strlen("primary"));
+            builder.add_tag("nameXX", 4, "Main Street", 11);
+        }
+        buffer.commit();
+    }
 
-        const osmium::TagList& tl = osmium::builder::build_tag_list(buffer, {
+    SECTION("with TagListBuilder from std::string") {
+        {
+            osmium::builder::TagListBuilder builder(buffer);
+            builder.add_tag(std::string("highway"), std::string("primary"));
+            const std::string source = "name";
+            std::string gps = "Main Street";
+            builder.add_tag(source, gps);
+        }
+        buffer.commit();
+    }
+
+    SECTION("with build_tag_list from initializer list") {
+        osmium::builder::build_tag_list(buffer, {
             { "highway", "primary" },
-            { "name", "Main Street" },
-            { "source", "GPS" }
+            { "name", "Main Street" }
         });
-
-        REQUIRE(osmium::item_type::tag_list == tl.type());
-        REQUIRE(3 == tl.size());
-        REQUIRE(std::string("highway") == tl.begin()->key());
-        REQUIRE(std::string("primary") == tl.begin()->value());
     }
 
-    SECTION("can_be_created_from_map") {
-        osmium::memory::Buffer buffer(10240);
-
-        const osmium::TagList& tl = osmium::builder::build_tag_list_from_map(buffer, std::map<const char*, const char*>({
+    SECTION("with build_tag_list_from_map") {
+        osmium::builder::build_tag_list_from_map(buffer, std::map<const char*, const char*>({
             { "highway", "primary" },
             { "name", "Main Street" }
         }));
-
-        REQUIRE(osmium::item_type::tag_list == tl.type());
-        REQUIRE(2 == tl.size());
-
-        if (std::string("highway") == tl.begin()->key()) {
-            REQUIRE(std::string("primary") == tl.begin()->value());
-            REQUIRE(std::string("name") == std::next(tl.begin(), 1)->key());
-            REQUIRE(std::string("Main Street") == std::next(tl.begin(), 1)->value());
-        } else {
-            REQUIRE(std::string("highway") == std::next(tl.begin(), 1)->key());
-            REQUIRE(std::string("primary") == std::next(tl.begin(), 1)->value());
-            REQUIRE(std::string("name") == tl.begin()->key());
-            REQUIRE(std::string("Main Street") == tl.begin()->value());
-        }
     }
 
-    SECTION("can_be_created_with_callback") {
-        osmium::memory::Buffer buffer(10240);
-
-        const osmium::TagList& tl = osmium::builder::build_tag_list_from_func(buffer, [](osmium::builder::TagListBuilder& tlb) {
+    SECTION("with build_tag_list_from_func") {
+        osmium::builder::build_tag_list_from_func(buffer, [](osmium::builder::TagListBuilder& tlb) {
             tlb.add_tag("highway", "primary");
-            tlb.add_tag("bridge", "true");
+            tlb.add_tag("name", "Main Street");
         });
-
-        REQUIRE(osmium::item_type::tag_list == tl.type());
-        REQUIRE(2 == tl.size());
-        REQUIRE(std::string("bridge") == std::next(tl.begin(), 1)->key());
-        REQUIRE(std::string("true") == std::next(tl.begin(), 1)->value());
     }
 
-    SECTION("returns_value_by_key") {
-        osmium::memory::Buffer buffer(10240);
+    const osmium::TagList& tl = *buffer.begin<osmium::TagList>();
+    REQUIRE(osmium::item_type::tag_list == tl.type());
+    REQUIRE(2 == tl.size());
 
-        const osmium::TagList& tl = osmium::builder::build_tag_list_from_func(buffer, [](osmium::builder::TagListBuilder& tlb) {
-            tlb.add_tag("highway", "primary");
-            tlb.add_tag("bridge", "true");
-        });
+    auto it = tl.begin();
+    REQUIRE(std::string("highway")     == it->key());
+    REQUIRE(std::string("primary")     == it->value());
+    ++it;
+    REQUIRE(std::string("name")        == it->key());
+    REQUIRE(std::string("Main Street") == it->value());
+    ++it;
+    REQUIRE(it == tl.end());
 
-        REQUIRE(std::string("primary") == tl.get_value_by_key("highway"));
-        REQUIRE(nullptr == tl.get_value_by_key("name"));
-        REQUIRE(std::string("foo") == tl.get_value_by_key("name", "foo"));
+    REQUIRE(std::string("primary") == tl.get_value_by_key("highway"));
+    REQUIRE(nullptr == tl.get_value_by_key("foo"));
+    REQUIRE(std::string("default") == tl.get_value_by_key("foo", "default"));
 
-        REQUIRE(std::string("true") == tl["bridge"]);
-    }
+    REQUIRE(std::string("Main Street") == tl["name"]);
+}
+
+TEST_CASE("empty keys and values are okay") {
+    osmium::memory::Buffer buffer(10240);
+
+    const osmium::TagList& tl = osmium::builder::build_tag_list(buffer, {
+        { "empty value", "" },
+        { "", "empty key" }
+    });
+
+    REQUIRE(osmium::item_type::tag_list == tl.type());
+    REQUIRE(2 == tl.size());
+
+    auto it = tl.begin();
+    REQUIRE(std::string("empty value") == it->key());
+    REQUIRE(std::string("")            == it->value());
+    ++it;
+    REQUIRE(std::string("")            == it->key());
+    REQUIRE(std::string("empty key")   == it->value());
+    ++it;
+    REQUIRE(it == tl.end());
 
+    REQUIRE(std::string("") == tl.get_value_by_key("empty value"));
+    REQUIRE(std::string("empty key") == tl.get_value_by_key(""));
 }
diff --git a/test/t/util/test_delta.cpp b/test/t/util/test_delta.cpp
index a698204..cebcca8 100644
--- a/test/t/util/test_delta.cpp
+++ b/test/t/util/test_delta.cpp
@@ -1,5 +1,7 @@
 #include "catch.hpp"
 
+#include <vector>
+
 #include <osmium/util/delta.hpp>
 
 TEST_CASE("delta encode") {
@@ -42,3 +44,25 @@ TEST_CASE("delta encode and decode") {
 
 }
 
+TEST_CASE("delta encode iterator") {
+    std::vector<int> data = { 4, 5, 13, 22, 12 };
+
+    auto l = [](std::vector<int>::const_iterator it) -> int {
+        return *it;
+    };
+
+    typedef osmium::util::DeltaEncodeIterator<std::vector<int>::const_iterator, decltype(l), int> it_type;
+    it_type it(data.begin(), data.end(), l);
+    it_type end(data.end(), data.end(), l);
+
+    REQUIRE(*it == 4);
+    ++it;
+    REQUIRE(*it++ == 1);
+    REQUIRE(*it == 8);
+    ++it;
+    REQUIRE(*it++ == 9);
+    REQUIRE(*it == -10);
+    ++it;
+    REQUIRE(it == end);
+}
+
diff --git a/test/t/util/test_memory_mapping.cpp b/test/t/util/test_memory_mapping.cpp
index 7f95405..29893f7 100644
--- a/test/t/util/test_memory_mapping.cpp
+++ b/test/t/util/test_memory_mapping.cpp
@@ -15,7 +15,7 @@ static const size_t huge = std::numeric_limits<size_t>::max();
 TEST_CASE("anonymous mapping") {
 
     SECTION("simple memory mapping should work") {
-        osmium::util::MemoryMapping mapping(1000);
+        osmium::util::MemoryMapping mapping(1000, osmium::util::MemoryMapping::mapping_mode::write_private);
         REQUIRE(mapping.get_addr() != nullptr);
 
         REQUIRE(mapping.size() >= 1000);
@@ -34,7 +34,7 @@ TEST_CASE("anonymous mapping") {
     }
 
     SECTION("memory mapping of zero length should work") {
-        osmium::util::MemoryMapping mapping(0);
+        osmium::util::MemoryMapping mapping(0, osmium::util::MemoryMapping::mapping_mode::write_private);
         REQUIRE(mapping.get_addr() != nullptr);
 
         REQUIRE(mapping.size() == osmium::util::get_pagesize());
@@ -44,13 +44,8 @@ TEST_CASE("anonymous mapping") {
         REQUIRE(!mapping);
     }
 
-    SECTION("memory mapping a huge area should fail") {
-        REQUIRE_THROWS_AS(osmium::util::MemoryMapping mapping(huge),
-            std::system_error);
-    }
-
     SECTION("moving a memory mapping should work") {
-        osmium::util::MemoryMapping mapping1(1000);
+        osmium::util::MemoryMapping mapping1(1000, osmium::util::MemoryMapping::mapping_mode::write_private);
         int* addr1 = mapping1.get_addr<int>();
         *addr1 = 42;
 
@@ -68,8 +63,8 @@ TEST_CASE("anonymous mapping") {
     }
 
     SECTION("move assignment should work") {
-        osmium::util::MemoryMapping mapping1(1000);
-        osmium::util::MemoryMapping mapping2(1000);
+        osmium::util::MemoryMapping mapping1(1000, osmium::util::MemoryMapping::mapping_mode::write_private);
+        osmium::util::MemoryMapping mapping2(1000, osmium::util::MemoryMapping::mapping_mode::write_private);
 
         REQUIRE(!!mapping1);
         REQUIRE(!!mapping2);
@@ -90,7 +85,7 @@ TEST_CASE("anonymous mapping") {
 
 #ifdef __linux__
     SECTION("remapping to larger size should work") {
-        osmium::util::MemoryMapping mapping(1000);
+        osmium::util::MemoryMapping mapping(1000, osmium::util::MemoryMapping::mapping_mode::write_private);
         REQUIRE(mapping.size() >= 1000);
 
         size_t size1 = mapping.size();
@@ -106,7 +101,7 @@ TEST_CASE("anonymous mapping") {
     }
 
     SECTION("remapping to smaller size should work") {
-        osmium::util::MemoryMapping mapping(8000);
+        osmium::util::MemoryMapping mapping(8000, osmium::util::MemoryMapping::mapping_mode::write_private);
         REQUIRE(mapping.size() >= 1000);
 
         size_t size1 = mapping.size();
@@ -134,7 +129,7 @@ TEST_CASE("file-based mapping") {
         osmium::util::resize_file(fd, 100);
 
         {
-            osmium::util::MemoryMapping mapping(100, true, fd);
+            osmium::util::MemoryMapping mapping(100, osmium::util::MemoryMapping::mapping_mode::write_shared, fd);
             REQUIRE(mapping.writable());
 
             REQUIRE(!!mapping);
@@ -148,7 +143,7 @@ TEST_CASE("file-based mapping") {
         REQUIRE(osmium::util::file_size(fd) == 100);
 
         {
-            osmium::util::MemoryMapping mapping(100, false, fd);
+            osmium::util::MemoryMapping mapping(100, osmium::util::MemoryMapping::mapping_mode::readonly, fd);
             REQUIRE(!mapping.writable());
 
             REQUIRE(!!mapping);
@@ -162,12 +157,48 @@ TEST_CASE("file-based mapping") {
         REQUIRE(0 == unlink(filename));
     }
 
+    SECTION("writing to a privately mapped file should work") {
+        char filename[] = "test_mmap_write_XXXXXX";
+        const int fd = mkstemp(filename);
+        REQUIRE(fd > 0);
+
+        osmium::util::resize_file(fd, 100);
+
+        {
+            osmium::util::MemoryMapping mapping(100, osmium::util::MemoryMapping::mapping_mode::write_private, fd);
+            REQUIRE(mapping.writable());
+
+            REQUIRE(!!mapping);
+            REQUIRE(mapping.size() >= 100);
+
+            *mapping.get_addr<int>() = 1234;
+
+            mapping.unmap();
+        }
+
+        REQUIRE(osmium::util::file_size(fd) == 100);
+
+        {
+            osmium::util::MemoryMapping mapping(100, osmium::util::MemoryMapping::mapping_mode::readonly, fd);
+            REQUIRE(!mapping.writable());
+
+            REQUIRE(!!mapping);
+            REQUIRE(mapping.size() >= 100);
+            REQUIRE(*mapping.get_addr<int>() == 0); // should not see the value set above
+
+            mapping.unmap();
+        }
+
+        REQUIRE(0 == close(fd));
+        REQUIRE(0 == unlink(filename));
+    }
+
     SECTION("remapping to larger size should work") {
         char filename[] = "test_mmap_grow_XXXXXX";
         const int fd = mkstemp(filename);
         REQUIRE(fd > 0);
 
-        osmium::util::MemoryMapping mapping(100, true, fd);
+        osmium::util::MemoryMapping mapping(100, osmium::util::MemoryMapping::mapping_mode::write_shared, fd);
         REQUIRE(mapping.size() >= 100);
         size_t size1 = mapping.size();
 
@@ -193,7 +224,7 @@ TEST_CASE("file-based mapping") {
         REQUIRE(fd > 0);
 
         {
-            osmium::util::MemoryMapping mapping(8000, true, fd);
+            osmium::util::MemoryMapping mapping(8000, osmium::util::MemoryMapping::mapping_mode::write_shared, fd);
             REQUIRE(mapping.size() >= 8000);
             size_t size1 = mapping.size();
 
@@ -230,11 +261,6 @@ TEST_CASE("typed anonymous mapping") {
         mapping.unmap(); // second unmap is okay
     }
 
-    SECTION("memory mapping a huge area should fail") {
-        REQUIRE_THROWS_AS(osmium::util::TypedMemoryMapping<uint32_t> mapping(huge),
-            std::system_error);
-    }
-
     SECTION("moving a memory mapping should work") {
         osmium::util::TypedMemoryMapping<uint32_t> mapping1(1000);
         uint32_t* addr1 = mapping1.begin();
@@ -314,7 +340,7 @@ TEST_CASE("typed file-based mapping") {
         osmium::util::resize_file(fd, 100);
 
         {
-            osmium::util::TypedMemoryMapping<uint32_t> mapping(100, true, fd);
+            osmium::util::TypedMemoryMapping<uint32_t> mapping(100, osmium::util::MemoryMapping::mapping_mode::write_shared, fd);
             REQUIRE(mapping.writable());
 
             REQUIRE(!!mapping);
@@ -326,7 +352,7 @@ TEST_CASE("typed file-based mapping") {
         }
 
         {
-            osmium::util::TypedMemoryMapping<uint32_t> mapping(100, false, fd);
+            osmium::util::TypedMemoryMapping<uint32_t> mapping(100, osmium::util::MemoryMapping::mapping_mode::readonly, fd);
             REQUIRE(!mapping.writable());
 
             REQUIRE(!!mapping);

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



More information about the Pkg-grass-devel mailing list