Import protobuf-parse 3.2.0 am: 5012cb7225 am: 0048aefa57 am: bfe22844c0

Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/protobuf-parse/+/2478018

Change-Id: Ief2e19ac460ba09a8fda28040f18dff99458fde7
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..7aff3f1
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "7155092f3df112159d55132081937e1fe5c30490"
+  },
+  "path_in_vcs": "protobuf-parse"
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..6c38ac9
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,53 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_library {
+    name: "libprotobuf_parse",
+    host_supported: true,
+    crate_name: "protobuf_parse",
+    cargo_env_compat: true,
+    cargo_pkg_version: "3.2.0",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libanyhow",
+        "libindexmap",
+        "liblog_rust",
+        "libprotobuf",
+        "libprotobuf_support",
+        "libtempfile",
+        "libthiserror",
+        "libwhich",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
+
+rust_binary {
+    name: "parse_and_typecheck",
+    host_supported: true,
+    crate_name: "parse_and_typecheck",
+    cargo_env_compat: true,
+    cargo_pkg_version: "3.2.0",
+    srcs: ["src/bin/parse-and-typecheck.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libanyhow",
+        "libindexmap",
+        "liblog_rust",
+        "libprotobuf",
+        "libprotobuf_parse",
+        "libprotobuf_support",
+        "libtempfile",
+        "libthiserror",
+        "libwhich",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..29d4f33
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,242 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "fastrand"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.133"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "protobuf"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e"
+dependencies = [
+ "once_cell",
+ "protobuf-support",
+ "thiserror",
+]
+
+[[package]]
+name = "protobuf-parse"
+version = "3.2.0"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "log",
+ "protobuf",
+ "protobuf-support",
+ "tempfile",
+ "thiserror",
+ "which",
+]
+
+[[package]]
+name = "protobuf-support"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
+
+[[package]]
+name = "which"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
+dependencies = [
+ "either",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..862c5a1
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,62 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "protobuf-parse"
+version = "3.2.0"
+authors = ["Stepan Koltsov <[email protected]>"]
+description = """
+Parse `.proto` files.
+
+Files are parsed into a `protobuf::descriptor::FileDescriptorSet` object using either:
+* pure rust parser (no dependencies)
+* `protoc` binary (more reliable and compatible with Google's implementation)
+"""
+homepage = "https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-parse/"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-parse/"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[lib]
+doctest = false
+
+[[bin]]
+name = "parse-and-typecheck"
+path = "src/bin/parse-and-typecheck.rs"
+test = false
+
+[dependencies.anyhow]
+version = "1.0.53"
+
+[dependencies.indexmap]
+version = "1.8.0"
+
+[dependencies.log]
+version = "0.4"
+
+[dependencies.protobuf]
+version = "=3.2.0"
+
+[dependencies.protobuf-support]
+version = "=3.2.0"
+
+[dependencies.tempfile]
+version = "3.2.0"
+
+[dependencies.thiserror]
+version = "1.0.30"
+
+[dependencies.which]
+version = "4.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..4159196
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,39 @@
+[package]
+name = "protobuf-parse"
+version = "3.2.0"
+edition = "2021"
+authors = ["Stepan Koltsov <[email protected]>"]
+license = "MIT"
+homepage = "https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-parse/"
+repository = "https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-parse/"
+description = """
+Parse `.proto` files.
+
+Files are parsed into a `protobuf::descriptor::FileDescriptorSet` object using either:
+* pure rust parser (no dependencies)
+* `protoc` binary (more reliable and compatible with Google's implementation)
+"""
+
+[dependencies]
+tempfile  = "3.2.0"
+log       = "0.4"
+which     = "4.0"
+anyhow    = "1.0.53"
+thiserror = "1.0.30"
+indexmap  = "1.8.0"
+
+protobuf = { path = "../protobuf", version = "=3.2.0" }
+protobuf-support = { path = "../protobuf-support", version = "=3.2.0" }
+
+[lib]
+# TODO: figure out what to do with bundled linked_hash_map
+doctest = false
+
+[[bin]]
+
+name = "parse-and-typecheck"
+path = "src/bin/parse-and-typecheck.rs"
+test = false
+
+[package.metadata.docs.rs]
+all-features = true
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..acce639
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Stepan Koltsov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..acce639
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Stepan Koltsov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..ca74402
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "protobuf-parse"
+description: "()"
+third_party {
+  url {
+    type: HOMEPAGE
+    value: "https://crates.io/crates/protobuf-parse"
+  }
+  url {
+    type: ARCHIVE
+    value: "https://static.crates.io/crates/protobuf-parse/protobuf-parse-3.2.0.crate"
+  }
+  version: "3.2.0"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 2
+    day: 27
+  }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..54389c8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+<!-- cargo-sync-readme start -->
+
+# Parse `.proto` files
+
+Parse `.proto` file definitions, **not** the protobuf text format serialization.
+
+Files can be parsed using pure Rust parser (mod `pure`)
+or using the `protoc` command (mod `protoc`).
+
+This crate is not meant to be used directly, but rather through the `protobuf-codegen` crate.
+If you think this crate might be useful to you,
+please [consider creating an issue](https://github.com/stepancheg/rust-protobuf/issues/new),
+until that this crate is considered to have **no stable API**.
+
+<!-- cargo-sync-readme end -->
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..81b61d1
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,5 @@
+{
+  "dependencies": true,
+  "device": true,
+  "run": true
+}
diff --git a/examples/file-descriptor-out-compare.rs b/examples/file-descriptor-out-compare.rs
new file mode 100644
index 0000000..5eabc10
--- /dev/null
+++ b/examples/file-descriptor-out-compare.rs
@@ -0,0 +1,42 @@
+use std::env;
+use std::fs;
+
+use protobuf::text_format;
+use protobuf_parse::Parser;
+
+enum Which {
+    Protoc,
+    Pure,
+}
+
+fn main() {
+    let args = env::args().skip(1).collect::<Vec<_>>();
+    let args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
+    let (path, out_protoc, out_pure) = match args.as_slice() {
+        // Just invoke protoc.
+        [path, out_protoc, out_pure] => (path, out_protoc, out_pure),
+        _ => panic!("wrong args"),
+    };
+
+    for which in [Which::Pure, Which::Protoc] {
+        let mut parser = Parser::new();
+        match which {
+            Which::Protoc => {
+                parser.protoc();
+            }
+            Which::Pure => {
+                parser.pure();
+            }
+        }
+
+        parser.input(path);
+        parser.include(".");
+        let fds = parser.file_descriptor_set().unwrap();
+        let fds = text_format::print_to_string_pretty(&fds);
+        let out = match which {
+            Which::Protoc => out_protoc,
+            Which::Pure => out_pure,
+        };
+        fs::write(out, fds).unwrap();
+    }
+}
diff --git a/src/bin/parse-and-typecheck.rs b/src/bin/parse-and-typecheck.rs
new file mode 100644
index 0000000..ffa8e72
--- /dev/null
+++ b/src/bin/parse-and-typecheck.rs
@@ -0,0 +1,37 @@
+use std::env;
+use std::path::PathBuf;
+use std::process::exit;
+
+use protobuf_parse::Parser;
+
+fn main() {
+    let args = env::args_os()
+        .skip(1)
+        .map(PathBuf::from)
+        .collect::<Vec<_>>();
+
+    if args.len() != 2 {
+        eprintln!(
+            "usage: {} <input.proto> <include>",
+            env::args().next().unwrap()
+        );
+        exit(1);
+    }
+
+    eprintln!(
+        "{} is not a part of public interface",
+        env::args().next().unwrap()
+    );
+
+    assert!(args.len() >= 2);
+    let (input, includes) = args.split_at(1);
+    let t = Parser::new()
+        .pure()
+        .includes(includes)
+        .inputs(input)
+        .parse_and_typecheck()
+        .expect("parse_and_typecheck");
+    for fd in t.file_descriptors {
+        println!("{:#?}", fd);
+    }
+}
diff --git a/src/case_convert.rs b/src/case_convert.rs
new file mode 100644
index 0000000..6da0747
--- /dev/null
+++ b/src/case_convert.rs
@@ -0,0 +1,55 @@
+// copy-paste from Google Protobuf
+// must be kept in sync with Google for JSON interop
+#[doc(hidden)]
+pub fn camel_case(input: &str) -> String {
+    let mut capitalize_next = true;
+    let mut result = String::new();
+    result.reserve(input.len());
+
+    for c in input.chars() {
+        if c == '_' {
+            capitalize_next = true;
+        } else if capitalize_next {
+            result.push(c.to_ascii_uppercase());
+            capitalize_next = false;
+        } else {
+            result.push(c);
+        }
+    }
+
+    result
+}
+
+#[doc(hidden)]
+pub fn snake_case(input: &str) -> String {
+    let mut result = String::new();
+
+    let mut last_lower = false;
+
+    for c in input.chars() {
+        if c.is_ascii_uppercase() && last_lower {
+            result.push('_');
+        }
+        result.push(c.to_ascii_lowercase());
+        last_lower = c.is_lowercase();
+    }
+
+    result
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_camel_case() {
+        assert_eq!("FooBarBazQuxQUUX", camel_case("foo_barBaz_QuxQUUX"));
+        assert_eq!("FooBarBazQuxQUUX", camel_case("Foo_barBaz_QuxQUUX"));
+    }
+
+    #[test]
+    fn test_snake_case() {
+        assert_eq!("foo_bar_baz_qux_quux", snake_case("foo_barBaz_QuxQUUX"));
+        assert_eq!("foo_bar_baz_qux_quux", snake_case("Foo_barBaz_QuxQUUX"));
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..1e5510f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,49 @@
+//! # Parse `.proto` files
+//!
+//! Parse `.proto` file definitions, **not** the protobuf text format serialization.
+//!
+//! Files can be parsed using pure Rust parser (mod `pure`)
+//! or using the `protoc` command (mod `protoc`).
+//!
+//! This crate is not meant to be used directly, but rather through the `protobuf-codegen` crate.
+//! If you think this crate might be useful to you,
+//! please [consider creating an issue](https://github.com/stepancheg/rust-protobuf/issues/new),
+//! until that this crate is considered to have **no stable API**.
+
+extern crate core;
+
+mod case_convert;
+mod parse_and_typecheck;
+mod parser;
+mod path;
+mod proto;
+mod proto_path;
+mod protobuf_abs_path;
+mod protobuf_ident;
+mod protobuf_path;
+mod protobuf_rel_path;
+pub(crate) mod protoc;
+pub mod pure;
+mod rel_path;
+mod test_against_protobuf_protos;
+mod which_parser;
+
+// Public API
+// Non-public API used by codegen crate.
+pub use case_convert::*;
+pub use parse_and_typecheck::*;
+pub use parser::Parser;
+pub use proto_path::*;
+use protobuf::reflect::FileDescriptor;
+pub use protobuf_abs_path::*;
+pub use protobuf_ident::*;
+pub use protobuf_rel_path::*;
+
+use crate::pure::model;
+
+#[derive(Clone)]
+pub(crate) struct FileDescriptorPair {
+    pub(crate) parsed: model::FileDescriptor,
+    pub(crate) descriptor_proto: protobuf::descriptor::FileDescriptorProto,
+    pub(crate) descriptor: FileDescriptor,
+}
diff --git a/src/parse_and_typecheck.rs b/src/parse_and_typecheck.rs
new file mode 100644
index 0000000..3c67cbf
--- /dev/null
+++ b/src/parse_and_typecheck.rs
@@ -0,0 +1,80 @@
+use crate::ProtoPathBuf;
+
+/// Result of parsing `.proto` files.
+#[doc(hidden)]
+pub struct ParsedAndTypechecked {
+    /// One entry for each input `.proto` file.
+    pub relative_paths: Vec<ProtoPathBuf>,
+    /// All parsed `.proto` files including dependencies of input files.
+    pub file_descriptors: Vec<protobuf::descriptor::FileDescriptorProto>,
+    /// Description of the parser (e.g. to include in generated files).
+    pub parser: String,
+}
+
+#[cfg(test)]
+mod test {
+    use std::collections::HashSet;
+    use std::fs;
+
+    use crate::Parser;
+
+    #[test]
+    fn parse_and_typecheck() {
+        let dir = tempfile::tempdir().unwrap();
+        let a_proto = dir.path().join("a.proto");
+        let b_proto = dir.path().join("b.proto");
+        fs::write(&a_proto, "syntax = 'proto3'; message Apple {}").unwrap();
+        fs::write(
+            &b_proto,
+            "syntax = 'proto3'; import 'a.proto'; message Banana { Apple a = 1; }",
+        )
+        .unwrap();
+
+        let pure = Parser::new()
+            .pure()
+            .include(dir.path())
+            .input(&b_proto)
+            .parse_and_typecheck()
+            .unwrap();
+        let protoc = Parser::new()
+            .protoc()
+            .include(dir.path())
+            .input(&b_proto)
+            .parse_and_typecheck()
+            .unwrap();
+
+        assert_eq!(pure.relative_paths, protoc.relative_paths);
+        assert_eq!(2, pure.file_descriptors.len());
+        assert_eq!(2, protoc.file_descriptors.len());
+        // TODO: make result more deterministic
+        assert_eq!(
+            HashSet::from(["a.proto", "b.proto"]),
+            pure.file_descriptors.iter().map(|d| d.name()).collect()
+        );
+        assert_eq!(
+            HashSet::from(["a.proto", "b.proto"]),
+            protoc.file_descriptors.iter().map(|d| d.name()).collect()
+        );
+        assert_eq!(1, protoc.file_descriptors[0].message_type.len());
+        assert_eq!(1, pure.file_descriptors[0].message_type.len());
+        assert_eq!(
+            "Banana",
+            pure.file_descriptors
+                .iter()
+                .find(|d| d.name() == "b.proto")
+                .unwrap()
+                .message_type[0]
+                .name()
+        );
+        assert_eq!(
+            "Banana",
+            protoc
+                .file_descriptors
+                .iter()
+                .find(|d| d.name() == "b.proto")
+                .unwrap()
+                .message_type[0]
+                .name()
+        );
+    }
+}
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..e642ab4
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,126 @@
+use std::collections::HashSet;
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::path::Path;
+use std::path::PathBuf;
+
+use anyhow::Context;
+use protobuf::descriptor::FileDescriptorSet;
+
+use crate::protoc;
+use crate::pure;
+use crate::which_parser::WhichParser;
+use crate::ParsedAndTypechecked;
+
+/// Configure and invoke `.proto` parser.
+#[derive(Default, Debug)]
+pub struct Parser {
+    which_parser: WhichParser,
+    pub(crate) includes: Vec<PathBuf>,
+    pub(crate) inputs: Vec<PathBuf>,
+    pub(crate) protoc: Option<PathBuf>,
+    pub(crate) protoc_extra_args: Vec<OsString>,
+    pub(crate) capture_stderr: bool,
+}
+
+impl Parser {
+    /// Create new default configured parser.
+    pub fn new() -> Parser {
+        Parser::default()
+    }
+
+    /// Use pure rust parser.
+    pub fn pure(&mut self) -> &mut Self {
+        self.which_parser = WhichParser::Pure;
+        self
+    }
+
+    /// Use `protoc` for parsing.
+    pub fn protoc(&mut self) -> &mut Self {
+        self.which_parser = WhichParser::Protoc;
+        self
+    }
+
+    /// Add an include directory.
+    pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
+        self.includes.push(include.as_ref().to_owned());
+        self
+    }
+
+    /// Add include directories.
+    pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
+        for include in includes {
+            self.include(include);
+        }
+        self
+    }
+
+    /// Append a `.proto` file path to compile
+    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
+        self.inputs.push(input.as_ref().to_owned());
+        self
+    }
+
+    /// Append multiple `.proto` file paths to compile
+    pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
+        for input in inputs {
+            self.input(input);
+        }
+        self
+    }
+
+    /// Specify `protoc` path used for parsing.
+    ///
+    /// This is ignored if pure rust parser is used.
+    pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self {
+        self.protoc = Some(protoc.to_owned());
+        self
+    }
+
+    /// Extra arguments to pass to `protoc` command (like experimental options).
+    ///
+    /// This is ignored if pure rust parser is used.
+    pub fn protoc_extra_args(
+        &mut self,
+        args: impl IntoIterator<Item = impl AsRef<OsStr>>,
+    ) -> &mut Self {
+        self.protoc_extra_args = args.into_iter().map(|s| s.as_ref().to_owned()).collect();
+        self
+    }
+
+    /// Capture stderr and return it in error.
+    ///
+    /// This option applies only to `protoc` parser.
+    /// By default `protoc` stderr is inherited from this process stderr.
+    pub fn capture_stderr(&mut self) -> &mut Self {
+        self.capture_stderr = true;
+        self
+    }
+
+    /// Parse `.proto` files and typecheck them using pure Rust parser of `protoc` command.
+    pub fn parse_and_typecheck(&self) -> anyhow::Result<ParsedAndTypechecked> {
+        match &self.which_parser {
+            WhichParser::Pure => {
+                pure::parse_and_typecheck::parse_and_typecheck(&self).context("using pure parser")
+            }
+            WhichParser::Protoc => protoc::parse_and_typecheck::parse_and_typecheck(&self)
+                .context("using protoc parser"),
+        }
+    }
+
+    /// Parse and convert result to `FileDescriptorSet`.
+    pub fn file_descriptor_set(&self) -> anyhow::Result<FileDescriptorSet> {
+        let mut generated = self.parse_and_typecheck()?;
+        let relative_paths: HashSet<_> = generated
+            .relative_paths
+            .iter()
+            .map(|path| path.to_string())
+            .collect();
+        generated
+            .file_descriptors
+            .retain(|fd| relative_paths.contains(fd.name()));
+        let mut fds = FileDescriptorSet::new();
+        fds.file = generated.file_descriptors;
+        Ok(fds)
+    }
+}
diff --git a/src/path.rs b/src/path.rs
new file mode 100644
index 0000000..832cba6
--- /dev/null
+++ b/src/path.rs
@@ -0,0 +1,28 @@
+use std::path::is_separator;
+
+use crate::proto_path::ProtoPath;
+
+pub(crate) fn fs_path_to_proto_path(path: &ProtoPath) -> String {
+    path.to_str()
+        .chars()
+        .map(|c| if is_separator(c) { '/' } else { c })
+        .collect()
+}
+
+#[cfg(test)]
+mod test {
+    use crate::path::fs_path_to_proto_path;
+    use crate::ProtoPath;
+
+    #[test]
+    fn test_fs_path_to_proto_path() {
+        assert_eq!(
+            "foo.proto",
+            fs_path_to_proto_path(ProtoPath::new("foo.proto").unwrap())
+        );
+        assert_eq!(
+            "bar/foo.proto",
+            fs_path_to_proto_path(ProtoPath::new("bar/foo.proto").unwrap())
+        );
+    }
+}
diff --git a/src/proto/google/protobuf/any.proto b/src/proto/google/protobuf/any.proto
new file mode 100644
index 0000000..6ed8a23
--- /dev/null
+++ b/src/proto/google/protobuf/any.proto
@@ -0,0 +1,158 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "google.golang.org/protobuf/types/known/anypb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "AnyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// `Any` contains an arbitrary serialized protocol buffer message along with a
+// URL that describes the type of the serialized message.
+//
+// Protobuf library provides support to pack/unpack Any values in the form
+// of utility functions or additional generated methods of the Any type.
+//
+// Example 1: Pack and unpack a message in C++.
+//
+//     Foo foo = ...;
+//     Any any;
+//     any.PackFrom(foo);
+//     ...
+//     if (any.UnpackTo(&foo)) {
+//       ...
+//     }
+//
+// Example 2: Pack and unpack a message in Java.
+//
+//     Foo foo = ...;
+//     Any any = Any.pack(foo);
+//     ...
+//     if (any.is(Foo.class)) {
+//       foo = any.unpack(Foo.class);
+//     }
+//
+//  Example 3: Pack and unpack a message in Python.
+//
+//     foo = Foo(...)
+//     any = Any()
+//     any.Pack(foo)
+//     ...
+//     if any.Is(Foo.DESCRIPTOR):
+//       any.Unpack(foo)
+//       ...
+//
+//  Example 4: Pack and unpack a message in Go
+//
+//      foo := &pb.Foo{...}
+//      any, err := anypb.New(foo)
+//      if err != nil {
+//        ...
+//      }
+//      ...
+//      foo := &pb.Foo{}
+//      if err := any.UnmarshalTo(foo); err != nil {
+//        ...
+//      }
+//
+// The pack methods provided by protobuf library will by default use
+// 'type.googleapis.com/full.type.name' as the type URL and the unpack
+// methods only use the fully qualified type name after the last '/'
+// in the type URL, for example "foo.bar.com/x/y.z" will yield type
+// name "y.z".
+//
+//
+// JSON
+// ====
+// The JSON representation of an `Any` value uses the regular
+// representation of the deserialized, embedded message, with an
+// additional field `@type` which contains the type URL. Example:
+//
+//     package google.profile;
+//     message Person {
+//       string first_name = 1;
+//       string last_name = 2;
+//     }
+//
+//     {
+//       "@type": "type.googleapis.com/google.profile.Person",
+//       "firstName": <string>,
+//       "lastName": <string>
+//     }
+//
+// If the embedded message type is well-known and has a custom JSON
+// representation, that representation will be embedded adding a field
+// `value` which holds the custom JSON in addition to the `@type`
+// field. Example (for message [google.protobuf.Duration][]):
+//
+//     {
+//       "@type": "type.googleapis.com/google.protobuf.Duration",
+//       "value": "1.212s"
+//     }
+//
+message Any {
+  // A URL/resource name that uniquely identifies the type of the serialized
+  // protocol buffer message. This string must contain at least
+  // one "/" character. The last segment of the URL's path must represent
+  // the fully qualified name of the type (as in
+  // `path/google.protobuf.Duration`). The name should be in a canonical form
+  // (e.g., leading "." is not accepted).
+  //
+  // In practice, teams usually precompile into the binary all types that they
+  // expect it to use in the context of Any. However, for URLs which use the
+  // scheme `http`, `https`, or no scheme, one can optionally set up a type
+  // server that maps type URLs to message definitions as follows:
+  //
+  // * If no scheme is provided, `https` is assumed.
+  // * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+  //   value in binary format, or produce an error.
+  // * Applications are allowed to cache lookup results based on the
+  //   URL, or have them precompiled into a binary to avoid any
+  //   lookup. Therefore, binary compatibility needs to be preserved
+  //   on changes to types. (Use versioned type names to manage
+  //   breaking changes.)
+  //
+  // Note: this functionality is not currently available in the official
+  // protobuf release, and it is not used for type URLs beginning with
+  // type.googleapis.com.
+  //
+  // Schemes other than `http`, `https` (or the empty scheme) might be
+  // used with implementation specific semantics.
+  //
+  string type_url = 1;
+
+  // Must be a valid serialized protocol buffer of the above specified type.
+  bytes value = 2;
+}
diff --git a/src/proto/google/protobuf/api.proto b/src/proto/google/protobuf/api.proto
new file mode 100644
index 0000000..3d598fc
--- /dev/null
+++ b/src/proto/google/protobuf/api.proto
@@ -0,0 +1,208 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/source_context.proto";
+import "google/protobuf/type.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "ApiProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/apipb";
+
+// Api is a light-weight descriptor for an API Interface.
+//
+// Interfaces are also described as "protocol buffer services" in some contexts,
+// such as by the "service" keyword in a .proto file, but they are different
+// from API Services, which represent a concrete implementation of an interface
+// as opposed to simply a description of methods and bindings. They are also
+// sometimes simply referred to as "APIs" in other contexts, such as the name of
+// this message itself. See https://cloud.google.com/apis/design/glossary for
+// detailed terminology.
+message Api {
+  // The fully qualified name of this interface, including package name
+  // followed by the interface's simple name.
+  string name = 1;
+
+  // The methods of this interface, in unspecified order.
+  repeated Method methods = 2;
+
+  // Any metadata attached to the interface.
+  repeated Option options = 3;
+
+  // A version string for this interface. If specified, must have the form
+  // `major-version.minor-version`, as in `1.10`. If the minor version is
+  // omitted, it defaults to zero. If the entire version field is empty, the
+  // major version is derived from the package name, as outlined below. If the
+  // field is not empty, the version in the package name will be verified to be
+  // consistent with what is provided here.
+  //
+  // The versioning schema uses [semantic
+  // versioning](http://semver.org) where the major version number
+  // indicates a breaking change and the minor version an additive,
+  // non-breaking change. Both version numbers are signals to users
+  // what to expect from different versions, and should be carefully
+  // chosen based on the product plan.
+  //
+  // The major version is also reflected in the package name of the
+  // interface, which must end in `v<major-version>`, as in
+  // `google.feature.v1`. For major versions 0 and 1, the suffix can
+  // be omitted. Zero major versions must only be used for
+  // experimental, non-GA interfaces.
+  //
+  //
+  string version = 4;
+
+  // Source context for the protocol buffer service represented by this
+  // message.
+  SourceContext source_context = 5;
+
+  // Included interfaces. See [Mixin][].
+  repeated Mixin mixins = 6;
+
+  // The source syntax of the service.
+  Syntax syntax = 7;
+}
+
+// Method represents a method of an API interface.
+message Method {
+  // The simple name of this method.
+  string name = 1;
+
+  // A URL of the input message type.
+  string request_type_url = 2;
+
+  // If true, the request is streamed.
+  bool request_streaming = 3;
+
+  // The URL of the output message type.
+  string response_type_url = 4;
+
+  // If true, the response is streamed.
+  bool response_streaming = 5;
+
+  // Any metadata attached to the method.
+  repeated Option options = 6;
+
+  // The source syntax of this method.
+  Syntax syntax = 7;
+}
+
+// Declares an API Interface to be included in this interface. The including
+// interface must redeclare all the methods from the included interface, but
+// documentation and options are inherited as follows:
+//
+// - If after comment and whitespace stripping, the documentation
+//   string of the redeclared method is empty, it will be inherited
+//   from the original method.
+//
+// - Each annotation belonging to the service config (http,
+//   visibility) which is not set in the redeclared method will be
+//   inherited.
+//
+// - If an http annotation is inherited, the path pattern will be
+//   modified as follows. Any version prefix will be replaced by the
+//   version of the including interface plus the [root][] path if
+//   specified.
+//
+// Example of a simple mixin:
+//
+//     package google.acl.v1;
+//     service AccessControl {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v1/{resource=**}:getAcl";
+//       }
+//     }
+//
+//     package google.storage.v2;
+//     service Storage {
+//       rpc GetAcl(GetAclRequest) returns (Acl);
+//
+//       // Get a data record.
+//       rpc GetData(GetDataRequest) returns (Data) {
+//         option (google.api.http).get = "/v2/{resource=**}";
+//       }
+//     }
+//
+// Example of a mixin configuration:
+//
+//     apis:
+//     - name: google.storage.v2.Storage
+//       mixins:
+//       - name: google.acl.v1.AccessControl
+//
+// The mixin construct implies that all methods in `AccessControl` are
+// also declared with same name and request/response types in
+// `Storage`. A documentation generator or annotation processor will
+// see the effective `Storage.GetAcl` method after inheriting
+// documentation and annotations as follows:
+//
+//     service Storage {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v2/{resource=**}:getAcl";
+//       }
+//       ...
+//     }
+//
+// Note how the version in the path pattern changed from `v1` to `v2`.
+//
+// If the `root` field in the mixin is specified, it should be a
+// relative path under which inherited HTTP paths are placed. Example:
+//
+//     apis:
+//     - name: google.storage.v2.Storage
+//       mixins:
+//       - name: google.acl.v1.AccessControl
+//         root: acls
+//
+// This implies the following inherited HTTP annotation:
+//
+//     service Storage {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
+//       }
+//       ...
+//     }
+message Mixin {
+  // The fully qualified name of the interface which is included.
+  string name = 1;
+
+  // If non-empty specifies a path under which inherited HTTP paths
+  // are rooted.
+  string root = 2;
+}
diff --git a/src/proto/google/protobuf/compiler/plugin.proto b/src/proto/google/protobuf/compiler/plugin.proto
new file mode 100644
index 0000000..9242aac
--- /dev/null
+++ b/src/proto/google/protobuf/compiler/plugin.proto
@@ -0,0 +1,183 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: [email protected] (Kenton Varda)
+//
+// WARNING:  The plugin interface is currently EXPERIMENTAL and is subject to
+//   change.
+//
+// protoc (aka the Protocol Compiler) can be extended via plugins.  A plugin is
+// just a program that reads a CodeGeneratorRequest from stdin and writes a
+// CodeGeneratorResponse to stdout.
+//
+// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
+// of dealing with the raw protocol defined here.
+//
+// A plugin executable needs only to be placed somewhere in the path.  The
+// plugin should be named "protoc-gen-$NAME", and will then be used when the
+// flag "--${NAME}_out" is passed to protoc.
+
+syntax = "proto2";
+
+package google.protobuf.compiler;
+option java_package = "com.google.protobuf.compiler";
+option java_outer_classname = "PluginProtos";
+
+option go_package = "google.golang.org/protobuf/types/pluginpb";
+
+import "google/protobuf/descriptor.proto";
+
+// The version number of protocol compiler.
+message Version {
+  optional int32 major = 1;
+  optional int32 minor = 2;
+  optional int32 patch = 3;
+  // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should
+  // be empty for mainline stable releases.
+  optional string suffix = 4;
+}
+
+// An encoded CodeGeneratorRequest is written to the plugin's stdin.
+message CodeGeneratorRequest {
+  // The .proto files that were explicitly listed on the command-line.  The
+  // code generator should generate code only for these files.  Each file's
+  // descriptor will be included in proto_file, below.
+  repeated string file_to_generate = 1;
+
+  // The generator parameter passed on the command-line.
+  optional string parameter = 2;
+
+  // FileDescriptorProtos for all files in files_to_generate and everything
+  // they import.  The files will appear in topological order, so each file
+  // appears before any file that imports it.
+  //
+  // protoc guarantees that all proto_files will be written after
+  // the fields above, even though this is not technically guaranteed by the
+  // protobuf wire format.  This theoretically could allow a plugin to stream
+  // in the FileDescriptorProtos and handle them one by one rather than read
+  // the entire set into memory at once.  However, as of this writing, this
+  // is not similarly optimized on protoc's end -- it will store all fields in
+  // memory at once before sending them to the plugin.
+  //
+  // Type names of fields and extensions in the FileDescriptorProto are always
+  // fully qualified.
+  repeated FileDescriptorProto proto_file = 15;
+
+  // The version number of protocol compiler.
+  optional Version compiler_version = 3;
+
+}
+
+// The plugin writes an encoded CodeGeneratorResponse to stdout.
+message CodeGeneratorResponse {
+  // Error message.  If non-empty, code generation failed.  The plugin process
+  // should exit with status code zero even if it reports an error in this way.
+  //
+  // This should be used to indicate errors in .proto files which prevent the
+  // code generator from generating correct code.  Errors which indicate a
+  // problem in protoc itself -- such as the input CodeGeneratorRequest being
+  // unparseable -- should be reported by writing a message to stderr and
+  // exiting with a non-zero status code.
+  optional string error = 1;
+
+  // A bitmask of supported features that the code generator supports.
+  // This is a bitwise "or" of values from the Feature enum.
+  optional uint64 supported_features = 2;
+
+  // Sync with code_generator.h.
+  enum Feature {
+    FEATURE_NONE = 0;
+    FEATURE_PROTO3_OPTIONAL = 1;
+  }
+
+  // Represents a single generated file.
+  message File {
+    // The file name, relative to the output directory.  The name must not
+    // contain "." or ".." components and must be relative, not be absolute (so,
+    // the file cannot lie outside the output directory).  "/" must be used as
+    // the path separator, not "\".
+    //
+    // If the name is omitted, the content will be appended to the previous
+    // file.  This allows the generator to break large files into small chunks,
+    // and allows the generated text to be streamed back to protoc so that large
+    // files need not reside completely in memory at one time.  Note that as of
+    // this writing protoc does not optimize for this -- it will read the entire
+    // CodeGeneratorResponse before writing files to disk.
+    optional string name = 1;
+
+    // If non-empty, indicates that the named file should already exist, and the
+    // content here is to be inserted into that file at a defined insertion
+    // point.  This feature allows a code generator to extend the output
+    // produced by another code generator.  The original generator may provide
+    // insertion points by placing special annotations in the file that look
+    // like:
+    //   @@protoc_insertion_point(NAME)
+    // The annotation can have arbitrary text before and after it on the line,
+    // which allows it to be placed in a comment.  NAME should be replaced with
+    // an identifier naming the point -- this is what other generators will use
+    // as the insertion_point.  Code inserted at this point will be placed
+    // immediately above the line containing the insertion point (thus multiple
+    // insertions to the same point will come out in the order they were added).
+    // The double-@ is intended to make it unlikely that the generated code
+    // could contain things that look like insertion points by accident.
+    //
+    // For example, the C++ code generator places the following line in the
+    // .pb.h files that it generates:
+    //   // @@protoc_insertion_point(namespace_scope)
+    // This line appears within the scope of the file's package namespace, but
+    // outside of any particular class.  Another plugin can then specify the
+    // insertion_point "namespace_scope" to generate additional classes or
+    // other declarations that should be placed in this scope.
+    //
+    // Note that if the line containing the insertion point begins with
+    // whitespace, the same whitespace will be added to every line of the
+    // inserted text.  This is useful for languages like Python, where
+    // indentation matters.  In these languages, the insertion point comment
+    // should be indented the same amount as any inserted code will need to be
+    // in order to work correctly in that context.
+    //
+    // The code generator that generates the initial file and the one which
+    // inserts into it must both run as part of a single invocation of protoc.
+    // Code generators are executed in the order in which they appear on the
+    // command line.
+    //
+    // If |insertion_point| is present, |name| must also be present.
+    optional string insertion_point = 2;
+
+    // The file contents.
+    optional string content = 15;
+
+    // Information describing the file content being inserted. If an insertion
+    // point is used, this information will be appropriately offset and inserted
+    // into the code generation metadata for the generated files.
+    optional GeneratedCodeInfo generated_code_info = 16;
+  }
+  repeated File file = 15;
+}
diff --git a/src/proto/google/protobuf/descriptor.proto b/src/proto/google/protobuf/descriptor.proto
new file mode 100644
index 0000000..156e410
--- /dev/null
+++ b/src/proto/google/protobuf/descriptor.proto
@@ -0,0 +1,911 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: [email protected] (Kenton Varda)
+//  Based on original Protocol Buffers design by
+//  Sanjay Ghemawat, Jeff Dean, and others.
+//
+// The messages in this file describe the definitions found in .proto files.
+// A valid .proto file can be translated directly to a FileDescriptorProto
+// without any other information (e.g. without reading its imports).
+
+
+syntax = "proto2";
+
+package google.protobuf;
+
+option go_package = "google.golang.org/protobuf/types/descriptorpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DescriptorProtos";
+option csharp_namespace = "Google.Protobuf.Reflection";
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// descriptor.proto must be optimized for speed because reflection-based
+// algorithms don't work during bootstrapping.
+option optimize_for = SPEED;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+  repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+  optional string name = 1;     // file name, relative to root of source tree
+  optional string package = 2;  // e.g. "foo", "foo.bar", etc.
+
+  // Names of files imported by this file.
+  repeated string dependency = 3;
+  // Indexes of the public imported files in the dependency list above.
+  repeated int32 public_dependency = 10;
+  // Indexes of the weak imported files in the dependency list.
+  // For Google-internal migration only. Do not use.
+  repeated int32 weak_dependency = 11;
+
+  // All top-level definitions in this file.
+  repeated DescriptorProto message_type = 4;
+  repeated EnumDescriptorProto enum_type = 5;
+  repeated ServiceDescriptorProto service = 6;
+  repeated FieldDescriptorProto extension = 7;
+
+  optional FileOptions options = 8;
+
+  // This field contains optional information about the original source code.
+  // You may safely remove this entire field without harming runtime
+  // functionality of the descriptors -- the information is needed only by
+  // development tools.
+  optional SourceCodeInfo source_code_info = 9;
+
+  // The syntax of the proto file.
+  // The supported values are "proto2" and "proto3".
+  optional string syntax = 12;
+}
+
+// Describes a message type.
+message DescriptorProto {
+  optional string name = 1;
+
+  repeated FieldDescriptorProto field = 2;
+  repeated FieldDescriptorProto extension = 6;
+
+  repeated DescriptorProto nested_type = 3;
+  repeated EnumDescriptorProto enum_type = 4;
+
+  message ExtensionRange {
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Exclusive.
+
+    optional ExtensionRangeOptions options = 3;
+  }
+  repeated ExtensionRange extension_range = 5;
+
+  repeated OneofDescriptorProto oneof_decl = 8;
+
+  optional MessageOptions options = 7;
+
+  // Range of reserved tag numbers. Reserved tag numbers may not be used by
+  // fields or extension ranges in the same message. Reserved ranges may
+  // not overlap.
+  message ReservedRange {
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Exclusive.
+  }
+  repeated ReservedRange reserved_range = 9;
+  // Reserved field names, which may not be used by fields in the same message.
+  // A given name may only be reserved once.
+  repeated string reserved_name = 10;
+}
+
+message ExtensionRangeOptions {
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+  enum Type {
+    // 0 is reserved for errors.
+    // Order is weird for historical reasons.
+    TYPE_DOUBLE = 1;
+    TYPE_FLOAT = 2;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
+    // negative values are likely.
+    TYPE_INT64 = 3;
+    TYPE_UINT64 = 4;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
+    // negative values are likely.
+    TYPE_INT32 = 5;
+    TYPE_FIXED64 = 6;
+    TYPE_FIXED32 = 7;
+    TYPE_BOOL = 8;
+    TYPE_STRING = 9;
+    // Tag-delimited aggregate.
+    // Group type is deprecated and not supported in proto3. However, Proto3
+    // implementations should still be able to parse the group wire format and
+    // treat group fields as unknown fields.
+    TYPE_GROUP = 10;
+    TYPE_MESSAGE = 11;  // Length-delimited aggregate.
+
+    // New in version 2.
+    TYPE_BYTES = 12;
+    TYPE_UINT32 = 13;
+    TYPE_ENUM = 14;
+    TYPE_SFIXED32 = 15;
+    TYPE_SFIXED64 = 16;
+    TYPE_SINT32 = 17;  // Uses ZigZag encoding.
+    TYPE_SINT64 = 18;  // Uses ZigZag encoding.
+  }
+
+  enum Label {
+    // 0 is reserved for errors
+    LABEL_OPTIONAL = 1;
+    LABEL_REQUIRED = 2;
+    LABEL_REPEATED = 3;
+  }
+
+  optional string name = 1;
+  optional int32 number = 3;
+  optional Label label = 4;
+
+  // If type_name is set, this need not be set.  If both this and type_name
+  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+  optional Type type = 5;
+
+  // For message and enum types, this is the name of the type.  If the name
+  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
+  // rules are used to find the type (i.e. first the nested types within this
+  // message are searched, then within the parent, on up to the root
+  // namespace).
+  optional string type_name = 6;
+
+  // For extensions, this is the name of the type being extended.  It is
+  // resolved in the same manner as type_name.
+  optional string extendee = 2;
+
+  // For numeric types, contains the original text representation of the value.
+  // For booleans, "true" or "false".
+  // For strings, contains the default text contents (not escaped in any way).
+  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
+  // TODO(kenton):  Base-64 encode?
+  optional string default_value = 7;
+
+  // If set, gives the index of a oneof in the containing type's oneof_decl
+  // list.  This field is a member of that oneof.
+  optional int32 oneof_index = 9;
+
+  // JSON name of this field. The value is set by protocol compiler. If the
+  // user has set a "json_name" option on this field, that option's value
+  // will be used. Otherwise, it's deduced from the field's name by converting
+  // it to camelCase.
+  optional string json_name = 10;
+
+  optional FieldOptions options = 8;
+
+  // If true, this is a proto3 "optional". When a proto3 field is optional, it
+  // tracks presence regardless of field type.
+  //
+  // When proto3_optional is true, this field must be belong to a oneof to
+  // signal to old proto3 clients that presence is tracked for this field. This
+  // oneof is known as a "synthetic" oneof, and this field must be its sole
+  // member (each proto3 optional field gets its own synthetic oneof). Synthetic
+  // oneofs exist in the descriptor only, and do not generate any API. Synthetic
+  // oneofs must be ordered after all "real" oneofs.
+  //
+  // For message fields, proto3_optional doesn't create any semantic change,
+  // since non-repeated message fields always track presence. However it still
+  // indicates the semantic detail of whether the user wrote "optional" or not.
+  // This can be useful for round-tripping the .proto file. For consistency we
+  // give message fields a synthetic oneof also, even though it is not required
+  // to track presence. This is especially important because the parser can't
+  // tell if a field is a message or an enum, so it must always create a
+  // synthetic oneof.
+  //
+  // Proto2 optional fields do not set this flag, because they already indicate
+  // optional with `LABEL_OPTIONAL`.
+  optional bool proto3_optional = 17;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+  optional string name = 1;
+  optional OneofOptions options = 2;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+  optional string name = 1;
+
+  repeated EnumValueDescriptorProto value = 2;
+
+  optional EnumOptions options = 3;
+
+  // Range of reserved numeric values. Reserved values may not be used by
+  // entries in the same enum. Reserved ranges may not overlap.
+  //
+  // Note that this is distinct from DescriptorProto.ReservedRange in that it
+  // is inclusive such that it can appropriately represent the entire int32
+  // domain.
+  message EnumReservedRange {
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Inclusive.
+  }
+
+  // Range of reserved numeric values. Reserved numeric values may not be used
+  // by enum values in the same enum declaration. Reserved ranges may not
+  // overlap.
+  repeated EnumReservedRange reserved_range = 4;
+
+  // Reserved enum value names, which may not be reused. A given name may only
+  // be reserved once.
+  repeated string reserved_name = 5;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+  optional string name = 1;
+  optional int32 number = 2;
+
+  optional EnumValueOptions options = 3;
+}
+
+// Describes a service.
+message ServiceDescriptorProto {
+  optional string name = 1;
+  repeated MethodDescriptorProto method = 2;
+
+  optional ServiceOptions options = 3;
+}
+
+// Describes a method of a service.
+message MethodDescriptorProto {
+  optional string name = 1;
+
+  // Input and output type names.  These are resolved in the same way as
+  // FieldDescriptorProto.type_name, but must refer to a message type.
+  optional string input_type = 2;
+  optional string output_type = 3;
+
+  optional MethodOptions options = 4;
+
+  // Identifies if client streams multiple client messages
+  optional bool client_streaming = 5 [default = false];
+  // Identifies if server streams multiple server messages
+  optional bool server_streaming = 6 [default = false];
+}
+
+
+// ===================================================================
+// Options
+
+// Each of the definitions above may have "options" attached.  These are
+// just annotations which may cause code to be generated slightly differently
+// or may contain hints for code that manipulates protocol messages.
+//
+// Clients may define custom options as extensions of the *Options messages.
+// These extensions may not yet be known at parsing time, so the parser cannot
+// store the values in them.  Instead it stores them in a field in the *Options
+// message called uninterpreted_option. This field must have the same name
+// across all *Options messages. We then use this field to populate the
+// extensions when we build a descriptor, at which point all protos have been
+// parsed and so all extensions are known.
+//
+// Extension numbers for custom options may be chosen as follows:
+// * For options which will only be used within a single application or
+//   organization, or for experimental options, use field numbers 50000
+//   through 99999.  It is up to you to ensure that you do not use the
+//   same number for multiple options.
+// * For options which will be published and used publicly by multiple
+//   independent entities, e-mail [email protected]
+//   to reserve extension numbers. Simply provide your project name (e.g.
+//   Objective-C plugin) and your project website (if available) -- there's no
+//   need to explain how you intend to use them. Usually you only need one
+//   extension number. You can declare multiple options with only one extension
+//   number by putting them in a sub-message. See the Custom Options section of
+//   the docs for examples:
+//   https://developers.google.com/protocol-buffers/docs/proto#options
+//   If this turns out to be popular, a web service will be set up
+//   to automatically assign option numbers.
+
+message FileOptions {
+
+  // Sets the Java package where classes generated from this .proto will be
+  // placed.  By default, the proto package is used, but this is often
+  // inappropriate because proto packages do not normally start with backwards
+  // domain names.
+  optional string java_package = 1;
+
+
+  // Controls the name of the wrapper Java class generated for the .proto file.
+  // That class will always contain the .proto file's getDescriptor() method as
+  // well as any top-level extensions defined in the .proto file.
+  // If java_multiple_files is disabled, then all the other classes from the
+  // .proto file will be nested inside the single wrapper outer class.
+  optional string java_outer_classname = 8;
+
+  // If enabled, then the Java code generator will generate a separate .java
+  // file for each top-level message, enum, and service defined in the .proto
+  // file.  Thus, these types will *not* be nested inside the wrapper class
+  // named by java_outer_classname.  However, the wrapper class will still be
+  // generated to contain the file's getDescriptor() method as well as any
+  // top-level extensions defined in the file.
+  optional bool java_multiple_files = 10 [default = false];
+
+  // This option does nothing.
+  optional bool java_generate_equals_and_hash = 20 [deprecated=true];
+
+  // If set true, then the Java2 code generator will generate code that
+  // throws an exception whenever an attempt is made to assign a non-UTF-8
+  // byte sequence to a string field.
+  // Message reflection will do the same.
+  // However, an extension field still accepts non-UTF-8 byte sequences.
+  // This option has no effect on when used with the lite runtime.
+  optional bool java_string_check_utf8 = 27 [default = false];
+
+
+  // Generated classes can be optimized for speed or code size.
+  enum OptimizeMode {
+    SPEED = 1;         // Generate complete code for parsing, serialization,
+                       // etc.
+    CODE_SIZE = 2;     // Use ReflectionOps to implement these methods.
+    LITE_RUNTIME = 3;  // Generate code using MessageLite and the lite runtime.
+  }
+  optional OptimizeMode optimize_for = 9 [default = SPEED];
+
+  // Sets the Go package where structs generated from this .proto will be
+  // placed. If omitted, the Go package will be derived from the following:
+  //   - The basename of the package import path, if provided.
+  //   - Otherwise, the package statement in the .proto file, if present.
+  //   - Otherwise, the basename of the .proto file, without extension.
+  optional string go_package = 11;
+
+
+
+
+  // Should generic services be generated in each language?  "Generic" services
+  // are not specific to any particular RPC system.  They are generated by the
+  // main code generators in each language (without additional plugins).
+  // Generic services were the only kind of service generation supported by
+  // early versions of google.protobuf.
+  //
+  // Generic services are now considered deprecated in favor of using plugins
+  // that generate code specific to your particular RPC system.  Therefore,
+  // these default to false.  Old code which depends on generic services should
+  // explicitly set them to true.
+  optional bool cc_generic_services = 16 [default = false];
+  optional bool java_generic_services = 17 [default = false];
+  optional bool py_generic_services = 18 [default = false];
+  optional bool php_generic_services = 42 [default = false];
+
+  // Is this file deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for everything in the file, or it will be completely ignored; in the very
+  // least, this is a formalization for deprecating files.
+  optional bool deprecated = 23 [default = false];
+
+  // Enables the use of arenas for the proto messages in this file. This applies
+  // only to generated classes for C++.
+  optional bool cc_enable_arenas = 31 [default = true];
+
+
+  // Sets the objective c class prefix which is prepended to all objective c
+  // generated classes from this .proto. There is no default.
+  optional string objc_class_prefix = 36;
+
+  // Namespace for generated classes; defaults to the package.
+  optional string csharp_namespace = 37;
+
+  // By default Swift generators will take the proto package and CamelCase it
+  // replacing '.' with underscore and use that to prefix the types/symbols
+  // defined. When this options is provided, they will use this value instead
+  // to prefix the types/symbols defined.
+  optional string swift_prefix = 39;
+
+  // Sets the php class prefix which is prepended to all php generated classes
+  // from this .proto. Default is empty.
+  optional string php_class_prefix = 40;
+
+  // Use this option to change the namespace of php generated classes. Default
+  // is empty. When this option is empty, the package name will be used for
+  // determining the namespace.
+  optional string php_namespace = 41;
+
+  // Use this option to change the namespace of php generated metadata classes.
+  // Default is empty. When this option is empty, the proto file name will be
+  // used for determining the namespace.
+  optional string php_metadata_namespace = 44;
+
+  // Use this option to change the package of ruby generated classes. Default
+  // is empty. When this option is not set, the package name will be used for
+  // determining the ruby package.
+  optional string ruby_package = 45;
+
+
+  // The parser stores options it doesn't recognize here.
+  // See the documentation for the "Options" section above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message.
+  // See the documentation for the "Options" section above.
+  extensions 1000 to max;
+
+  reserved 38;
+}
+
+message MessageOptions {
+  // Set true to use the old proto1 MessageSet wire format for extensions.
+  // This is provided for backwards-compatibility with the MessageSet wire
+  // format.  You should not use this for any other reason:  It's less
+  // efficient, has fewer features, and is more complicated.
+  //
+  // The message must be defined exactly as follows:
+  //   message Foo {
+  //     option message_set_wire_format = true;
+  //     extensions 4 to max;
+  //   }
+  // Note that the message cannot have any defined fields; MessageSets only
+  // have extensions.
+  //
+  // All extensions of your type must be singular messages; e.g. they cannot
+  // be int32s, enums, or repeated messages.
+  //
+  // Because this is an option, the above two restrictions are not enforced by
+  // the protocol compiler.
+  optional bool message_set_wire_format = 1 [default = false];
+
+  // Disables the generation of the standard "descriptor()" accessor, which can
+  // conflict with a field of the same name.  This is meant to make migration
+  // from proto1 easier; new code should avoid fields named "descriptor".
+  optional bool no_standard_descriptor_accessor = 2 [default = false];
+
+  // Is this message deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the message, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating messages.
+  optional bool deprecated = 3 [default = false];
+
+  reserved 4, 5, 6;
+
+  // Whether the message is an automatically generated map entry type for the
+  // maps field.
+  //
+  // For maps fields:
+  //     map<KeyType, ValueType> map_field = 1;
+  // The parsed descriptor looks like:
+  //     message MapFieldEntry {
+  //         option map_entry = true;
+  //         optional KeyType key = 1;
+  //         optional ValueType value = 2;
+  //     }
+  //     repeated MapFieldEntry map_field = 1;
+  //
+  // Implementations may choose not to generate the map_entry=true message, but
+  // use a native map in the target language to hold the keys and values.
+  // The reflection APIs in such implementations still need to work as
+  // if the field is a repeated message field.
+  //
+  // NOTE: Do not set the option in .proto files. Always use the maps syntax
+  // instead. The option should only be implicitly set by the proto compiler
+  // parser.
+  optional bool map_entry = 7;
+
+  reserved 8;  // javalite_serializable
+  reserved 9;  // javanano_as_lite
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message FieldOptions {
+  // The ctype option instructs the C++ code generator to use a different
+  // representation of the field than it normally would.  See the specific
+  // options below.  This option is not yet implemented in the open source
+  // release -- sorry, we'll try to include it in a future version!
+  optional CType ctype = 1 [default = STRING];
+  enum CType {
+    // Default mode.
+    STRING = 0;
+
+    CORD = 1;
+
+    STRING_PIECE = 2;
+  }
+  // The packed option can be enabled for repeated primitive fields to enable
+  // a more efficient representation on the wire. Rather than repeatedly
+  // writing the tag and type for each element, the entire array is encoded as
+  // a single length-delimited blob. In proto3, only explicit setting it to
+  // false will avoid using packed encoding.
+  optional bool packed = 2;
+
+  // The jstype option determines the JavaScript type used for values of the
+  // field.  The option is permitted only for 64 bit integral and fixed types
+  // (int64, uint64, sint64, fixed64, sfixed64).  A field with jstype JS_STRING
+  // is represented as JavaScript string, which avoids loss of precision that
+  // can happen when a large value is converted to a floating point JavaScript.
+  // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to
+  // use the JavaScript "number" type.  The behavior of the default option
+  // JS_NORMAL is implementation dependent.
+  //
+  // This option is an enum to permit additional types to be added, e.g.
+  // goog.math.Integer.
+  optional JSType jstype = 6 [default = JS_NORMAL];
+  enum JSType {
+    // Use the default type.
+    JS_NORMAL = 0;
+
+    // Use JavaScript strings.
+    JS_STRING = 1;
+
+    // Use JavaScript numbers.
+    JS_NUMBER = 2;
+  }
+
+  // Should this field be parsed lazily?  Lazy applies only to message-type
+  // fields.  It means that when the outer message is initially parsed, the
+  // inner message's contents will not be parsed but instead stored in encoded
+  // form.  The inner message will actually be parsed when it is first accessed.
+  //
+  // This is only a hint.  Implementations are free to choose whether to use
+  // eager or lazy parsing regardless of the value of this option.  However,
+  // setting this option true suggests that the protocol author believes that
+  // using lazy parsing on this field is worth the additional bookkeeping
+  // overhead typically needed to implement it.
+  //
+  // This option does not affect the public interface of any generated code;
+  // all method signatures remain the same.  Furthermore, thread-safety of the
+  // interface is not affected by this option; const methods remain safe to
+  // call from multiple threads concurrently, while non-const methods continue
+  // to require exclusive access.
+  //
+  //
+  // Note that implementations may choose not to check required fields within
+  // a lazy sub-message.  That is, calling IsInitialized() on the outer message
+  // may return true even if the inner message has missing required fields.
+  // This is necessary because otherwise the inner message would have to be
+  // parsed in order to perform the check, defeating the purpose of lazy
+  // parsing.  An implementation which chooses not to check required fields
+  // must be consistent about it.  That is, for any particular sub-message, the
+  // implementation must either *always* check its required fields, or *never*
+  // check its required fields, regardless of whether or not the message has
+  // been parsed.
+  optional bool lazy = 5 [default = false];
+
+  // Is this field deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for accessors, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating fields.
+  optional bool deprecated = 3 [default = false];
+
+  // For Google-internal migration only. Do not use.
+  optional bool weak = 10 [default = false];
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+
+  reserved 4;  // removed jtype
+}
+
+message OneofOptions {
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumOptions {
+
+  // Set this option to true to allow mapping different tag names to the same
+  // value.
+  optional bool allow_alias = 2;
+
+  // Is this enum deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating enums.
+  optional bool deprecated = 3 [default = false];
+
+  reserved 5;  // javanano_as_lite
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumValueOptions {
+  // Is this enum value deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum value, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating enum values.
+  optional bool deprecated = 1 [default = false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message ServiceOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this service deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the service, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating services.
+  optional bool deprecated = 33 [default = false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message MethodOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this method deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the method, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating methods.
+  optional bool deprecated = 33 [default = false];
+
+  // Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
+  // or neither? HTTP based RPC implementation may choose GET verb for safe
+  // methods, and PUT verb for idempotent methods instead of the default POST.
+  enum IdempotencyLevel {
+    IDEMPOTENCY_UNKNOWN = 0;
+    NO_SIDE_EFFECTS = 1;  // implies idempotent
+    IDEMPOTENT = 2;       // idempotent, but may have side effects
+  }
+  optional IdempotencyLevel idempotency_level = 34
+      [default = IDEMPOTENCY_UNKNOWN];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+  // The name of the uninterpreted option.  Each string represents a segment in
+  // a dot-separated name.  is_extension is true iff a segment represents an
+  // extension (denoted with parentheses in options specs in .proto files).
+  // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
+  // "foo.(bar.baz).qux".
+  message NamePart {
+    required string name_part = 1;
+    required bool is_extension = 2;
+  }
+  repeated NamePart name = 2;
+
+  // The value of the uninterpreted option, in whatever type the tokenizer
+  // identified it as during parsing. Exactly one of these should be set.
+  optional string identifier_value = 3;
+  optional uint64 positive_int_value = 4;
+  optional int64 negative_int_value = 5;
+  optional double double_value = 6;
+  optional bytes string_value = 7;
+  optional string aggregate_value = 8;
+}
+
+// ===================================================================
+// Optional source code info
+
+// Encapsulates information about the original source file from which a
+// FileDescriptorProto was generated.
+message SourceCodeInfo {
+  // A Location identifies a piece of source code in a .proto file which
+  // corresponds to a particular definition.  This information is intended
+  // to be useful to IDEs, code indexers, documentation generators, and similar
+  // tools.
+  //
+  // For example, say we have a file like:
+  //   message Foo {
+  //     optional string foo = 1;
+  //   }
+  // Let's look at just the field definition:
+  //   optional string foo = 1;
+  //   ^       ^^     ^^  ^  ^^^
+  //   a       bc     de  f  ghi
+  // We have the following locations:
+  //   span   path               represents
+  //   [a,i)  [ 4, 0, 2, 0 ]     The whole field definition.
+  //   [a,b)  [ 4, 0, 2, 0, 4 ]  The label (optional).
+  //   [c,d)  [ 4, 0, 2, 0, 5 ]  The type (string).
+  //   [e,f)  [ 4, 0, 2, 0, 1 ]  The name (foo).
+  //   [g,h)  [ 4, 0, 2, 0, 3 ]  The number (1).
+  //
+  // Notes:
+  // - A location may refer to a repeated field itself (i.e. not to any
+  //   particular index within it).  This is used whenever a set of elements are
+  //   logically enclosed in a single code segment.  For example, an entire
+  //   extend block (possibly containing multiple extension definitions) will
+  //   have an outer location whose path refers to the "extensions" repeated
+  //   field without an index.
+  // - Multiple locations may have the same path.  This happens when a single
+  //   logical declaration is spread out across multiple places.  The most
+  //   obvious example is the "extend" block again -- there may be multiple
+  //   extend blocks in the same scope, each of which will have the same path.
+  // - A location's span is not always a subset of its parent's span.  For
+  //   example, the "extendee" of an extension declaration appears at the
+  //   beginning of the "extend" block and is shared by all extensions within
+  //   the block.
+  // - Just because a location's span is a subset of some other location's span
+  //   does not mean that it is a descendant.  For example, a "group" defines
+  //   both a type and a field in a single declaration.  Thus, the locations
+  //   corresponding to the type and field and their components will overlap.
+  // - Code which tries to interpret locations should probably be designed to
+  //   ignore those that it doesn't understand, as more types of locations could
+  //   be recorded in the future.
+  repeated Location location = 1;
+  message Location {
+    // Identifies which part of the FileDescriptorProto was defined at this
+    // location.
+    //
+    // Each element is a field number or an index.  They form a path from
+    // the root FileDescriptorProto to the place where the definition.  For
+    // example, this path:
+    //   [ 4, 3, 2, 7, 1 ]
+    // refers to:
+    //   file.message_type(3)  // 4, 3
+    //       .field(7)         // 2, 7
+    //       .name()           // 1
+    // This is because FileDescriptorProto.message_type has field number 4:
+    //   repeated DescriptorProto message_type = 4;
+    // and DescriptorProto.field has field number 2:
+    //   repeated FieldDescriptorProto field = 2;
+    // and FieldDescriptorProto.name has field number 1:
+    //   optional string name = 1;
+    //
+    // Thus, the above path gives the location of a field name.  If we removed
+    // the last element:
+    //   [ 4, 3, 2, 7 ]
+    // this path refers to the whole field declaration (from the beginning
+    // of the label to the terminating semicolon).
+    repeated int32 path = 1 [packed = true];
+
+    // Always has exactly three or four elements: start line, start column,
+    // end line (optional, otherwise assumed same as start line), end column.
+    // These are packed into a single field for efficiency.  Note that line
+    // and column numbers are zero-based -- typically you will want to add
+    // 1 to each before displaying to a user.
+    repeated int32 span = 2 [packed = true];
+
+    // If this SourceCodeInfo represents a complete declaration, these are any
+    // comments appearing before and after the declaration which appear to be
+    // attached to the declaration.
+    //
+    // A series of line comments appearing on consecutive lines, with no other
+    // tokens appearing on those lines, will be treated as a single comment.
+    //
+    // leading_detached_comments will keep paragraphs of comments that appear
+    // before (but not connected to) the current element. Each paragraph,
+    // separated by empty lines, will be one comment element in the repeated
+    // field.
+    //
+    // Only the comment content is provided; comment markers (e.g. //) are
+    // stripped out.  For block comments, leading whitespace and an asterisk
+    // will be stripped from the beginning of each line other than the first.
+    // Newlines are included in the output.
+    //
+    // Examples:
+    //
+    //   optional int32 foo = 1;  // Comment attached to foo.
+    //   // Comment attached to bar.
+    //   optional int32 bar = 2;
+    //
+    //   optional string baz = 3;
+    //   // Comment attached to baz.
+    //   // Another line attached to baz.
+    //
+    //   // Comment attached to qux.
+    //   //
+    //   // Another line attached to qux.
+    //   optional double qux = 4;
+    //
+    //   // Detached comment for corge. This is not leading or trailing comments
+    //   // to qux or corge because there are blank lines separating it from
+    //   // both.
+    //
+    //   // Detached comment for corge paragraph 2.
+    //
+    //   optional string corge = 5;
+    //   /* Block comment attached
+    //    * to corge.  Leading asterisks
+    //    * will be removed. */
+    //   /* Block comment attached to
+    //    * grault. */
+    //   optional int32 grault = 6;
+    //
+    //   // ignored detached comments.
+    optional string leading_comments = 3;
+    optional string trailing_comments = 4;
+    repeated string leading_detached_comments = 6;
+  }
+}
+
+// Describes the relationship between generated code and its original source
+// file. A GeneratedCodeInfo message is associated with only one generated
+// source file, but may contain references to different source .proto files.
+message GeneratedCodeInfo {
+  // An Annotation connects some span of text in generated code to an element
+  // of its generating .proto file.
+  repeated Annotation annotation = 1;
+  message Annotation {
+    // Identifies the element in the original source .proto file. This field
+    // is formatted the same as SourceCodeInfo.Location.path.
+    repeated int32 path = 1 [packed = true];
+
+    // Identifies the filesystem path to the original source .proto.
+    optional string source_file = 2;
+
+    // Identifies the starting offset in bytes in the generated code
+    // that relates to the identified object.
+    optional int32 begin = 3;
+
+    // Identifies the ending offset in bytes in the generated code that
+    // relates to the identified offset. The end offset should be one past
+    // the last relevant byte (so the length of the text = end - begin).
+    optional int32 end = 4;
+  }
+}
diff --git a/src/proto/google/protobuf/duration.proto b/src/proto/google/protobuf/duration.proto
new file mode 100644
index 0000000..81c3e36
--- /dev/null
+++ b/src/proto/google/protobuf/duration.proto
@@ -0,0 +1,116 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/durationpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DurationProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Duration represents a signed, fixed-length span of time represented
+// as a count of seconds and fractions of seconds at nanosecond
+// resolution. It is independent of any calendar and concepts like "day"
+// or "month". It is related to Timestamp in that the difference between
+// two Timestamp values is a Duration and it can be added or subtracted
+// from a Timestamp. Range is approximately +-10,000 years.
+//
+// # Examples
+//
+// Example 1: Compute Duration from two Timestamps in pseudo code.
+//
+//     Timestamp start = ...;
+//     Timestamp end = ...;
+//     Duration duration = ...;
+//
+//     duration.seconds = end.seconds - start.seconds;
+//     duration.nanos = end.nanos - start.nanos;
+//
+//     if (duration.seconds < 0 && duration.nanos > 0) {
+//       duration.seconds += 1;
+//       duration.nanos -= 1000000000;
+//     } else if (duration.seconds > 0 && duration.nanos < 0) {
+//       duration.seconds -= 1;
+//       duration.nanos += 1000000000;
+//     }
+//
+// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
+//
+//     Timestamp start = ...;
+//     Duration duration = ...;
+//     Timestamp end = ...;
+//
+//     end.seconds = start.seconds + duration.seconds;
+//     end.nanos = start.nanos + duration.nanos;
+//
+//     if (end.nanos < 0) {
+//       end.seconds -= 1;
+//       end.nanos += 1000000000;
+//     } else if (end.nanos >= 1000000000) {
+//       end.seconds += 1;
+//       end.nanos -= 1000000000;
+//     }
+//
+// Example 3: Compute Duration from datetime.timedelta in Python.
+//
+//     td = datetime.timedelta(days=3, minutes=10)
+//     duration = Duration()
+//     duration.FromTimedelta(td)
+//
+// # JSON Mapping
+//
+// In JSON format, the Duration type is encoded as a string rather than an
+// object, where the string ends in the suffix "s" (indicating seconds) and
+// is preceded by the number of seconds, with nanoseconds expressed as
+// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+// microsecond should be expressed in JSON format as "3.000001s".
+//
+//
+message Duration {
+  // Signed seconds of the span of time. Must be from -315,576,000,000
+  // to +315,576,000,000 inclusive. Note: these bounds are computed from:
+  // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
+  int64 seconds = 1;
+
+  // Signed fractions of a second at nanosecond resolution of the span
+  // of time. Durations less than one second are represented with a 0
+  // `seconds` field and a positive or negative `nanos` field. For durations
+  // of one second or more, a non-zero value for the `nanos` field must be
+  // of the same sign as the `seconds` field. Must be from -999,999,999
+  // to +999,999,999 inclusive.
+  int32 nanos = 2;
+}
diff --git a/src/proto/google/protobuf/empty.proto b/src/proto/google/protobuf/empty.proto
new file mode 100644
index 0000000..5f992de
--- /dev/null
+++ b/src/proto/google/protobuf/empty.proto
@@ -0,0 +1,52 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "google.golang.org/protobuf/types/known/emptypb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "EmptyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// A generic empty message that you can re-use to avoid defining duplicated
+// empty messages in your APIs. A typical example is to use it as the request
+// or the response type of an API method. For instance:
+//
+//     service Foo {
+//       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+//     }
+//
+// The JSON representation for `Empty` is empty JSON object `{}`.
+message Empty {}
diff --git a/src/proto/google/protobuf/field_mask.proto b/src/proto/google/protobuf/field_mask.proto
new file mode 100644
index 0000000..6b5104f
--- /dev/null
+++ b/src/proto/google/protobuf/field_mask.proto
@@ -0,0 +1,245 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "FieldMaskProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb";
+option cc_enable_arenas = true;
+
+// `FieldMask` represents a set of symbolic field paths, for example:
+//
+//     paths: "f.a"
+//     paths: "f.b.d"
+//
+// Here `f` represents a field in some root message, `a` and `b`
+// fields in the message found in `f`, and `d` a field found in the
+// message in `f.b`.
+//
+// Field masks are used to specify a subset of fields that should be
+// returned by a get operation or modified by an update operation.
+// Field masks also have a custom JSON encoding (see below).
+//
+// # Field Masks in Projections
+//
+// When used in the context of a projection, a response message or
+// sub-message is filtered by the API to only contain those fields as
+// specified in the mask. For example, if the mask in the previous
+// example is applied to a response message as follows:
+//
+//     f {
+//       a : 22
+//       b {
+//         d : 1
+//         x : 2
+//       }
+//       y : 13
+//     }
+//     z: 8
+//
+// The result will not contain specific values for fields x,y and z
+// (their value will be set to the default, and omitted in proto text
+// output):
+//
+//
+//     f {
+//       a : 22
+//       b {
+//         d : 1
+//       }
+//     }
+//
+// A repeated field is not allowed except at the last position of a
+// paths string.
+//
+// If a FieldMask object is not present in a get operation, the
+// operation applies to all fields (as if a FieldMask of all fields
+// had been specified).
+//
+// Note that a field mask does not necessarily apply to the
+// top-level response message. In case of a REST get operation, the
+// field mask applies directly to the response, but in case of a REST
+// list operation, the mask instead applies to each individual message
+// in the returned resource list. In case of a REST custom method,
+// other definitions may be used. Where the mask applies will be
+// clearly documented together with its declaration in the API.  In
+// any case, the effect on the returned resource/resources is required
+// behavior for APIs.
+//
+// # Field Masks in Update Operations
+//
+// A field mask in update operations specifies which fields of the
+// targeted resource are going to be updated. The API is required
+// to only change the values of the fields as specified in the mask
+// and leave the others untouched. If a resource is passed in to
+// describe the updated values, the API ignores the values of all
+// fields not covered by the mask.
+//
+// If a repeated field is specified for an update operation, new values will
+// be appended to the existing repeated field in the target resource. Note that
+// a repeated field is only allowed in the last position of a `paths` string.
+//
+// If a sub-message is specified in the last position of the field mask for an
+// update operation, then new value will be merged into the existing sub-message
+// in the target resource.
+//
+// For example, given the target message:
+//
+//     f {
+//       b {
+//         d: 1
+//         x: 2
+//       }
+//       c: [1]
+//     }
+//
+// And an update message:
+//
+//     f {
+//       b {
+//         d: 10
+//       }
+//       c: [2]
+//     }
+//
+// then if the field mask is:
+//
+//  paths: ["f.b", "f.c"]
+//
+// then the result will be:
+//
+//     f {
+//       b {
+//         d: 10
+//         x: 2
+//       }
+//       c: [1, 2]
+//     }
+//
+// An implementation may provide options to override this default behavior for
+// repeated and message fields.
+//
+// In order to reset a field's value to the default, the field must
+// be in the mask and set to the default value in the provided resource.
+// Hence, in order to reset all fields of a resource, provide a default
+// instance of the resource and set all fields in the mask, or do
+// not provide a mask as described below.
+//
+// If a field mask is not present on update, the operation applies to
+// all fields (as if a field mask of all fields has been specified).
+// Note that in the presence of schema evolution, this may mean that
+// fields the client does not know and has therefore not filled into
+// the request will be reset to their default. If this is unwanted
+// behavior, a specific service may require a client to always specify
+// a field mask, producing an error if not.
+//
+// As with get operations, the location of the resource which
+// describes the updated values in the request message depends on the
+// operation kind. In any case, the effect of the field mask is
+// required to be honored by the API.
+//
+// ## Considerations for HTTP REST
+//
+// The HTTP kind of an update operation which uses a field mask must
+// be set to PATCH instead of PUT in order to satisfy HTTP semantics
+// (PUT must only be used for full updates).
+//
+// # JSON Encoding of Field Masks
+//
+// In JSON, a field mask is encoded as a single string where paths are
+// separated by a comma. Fields name in each path are converted
+// to/from lower-camel naming conventions.
+//
+// As an example, consider the following message declarations:
+//
+//     message Profile {
+//       User user = 1;
+//       Photo photo = 2;
+//     }
+//     message User {
+//       string display_name = 1;
+//       string address = 2;
+//     }
+//
+// In proto a field mask for `Profile` may look as such:
+//
+//     mask {
+//       paths: "user.display_name"
+//       paths: "photo"
+//     }
+//
+// In JSON, the same mask is represented as below:
+//
+//     {
+//       mask: "user.displayName,photo"
+//     }
+//
+// # Field Masks and Oneof Fields
+//
+// Field masks treat fields in oneofs just as regular fields. Consider the
+// following message:
+//
+//     message SampleMessage {
+//       oneof test_oneof {
+//         string name = 4;
+//         SubMessage sub_message = 9;
+//       }
+//     }
+//
+// The field mask can be:
+//
+//     mask {
+//       paths: "name"
+//     }
+//
+// Or:
+//
+//     mask {
+//       paths: "sub_message"
+//     }
+//
+// Note that oneof type names ("test_oneof" in this case) cannot be used in
+// paths.
+//
+// ## Field Mask Verification
+//
+// The implementation of any API method which has a FieldMask type field in the
+// request should verify the included field paths, and return an
+// `INVALID_ARGUMENT` error if any path is unmappable.
+message FieldMask {
+  // The set of field mask paths.
+  repeated string paths = 1;
+}
diff --git a/src/proto/google/protobuf/source_context.proto b/src/proto/google/protobuf/source_context.proto
new file mode 100644
index 0000000..06bfc43
--- /dev/null
+++ b/src/proto/google/protobuf/source_context.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "SourceContextProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb";
+
+// `SourceContext` represents information about the source of a
+// protobuf element, like the file in which it is defined.
+message SourceContext {
+  // The path-qualified name of the .proto file that contained the associated
+  // protobuf element.  For example: `"google/protobuf/source_context.proto"`.
+  string file_name = 1;
+}
diff --git a/src/proto/google/protobuf/struct.proto b/src/proto/google/protobuf/struct.proto
new file mode 100644
index 0000000..0ac843c
--- /dev/null
+++ b/src/proto/google/protobuf/struct.proto
@@ -0,0 +1,95 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/structpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "StructProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// `Struct` represents a structured data value, consisting of fields
+// which map to dynamically typed values. In some languages, `Struct`
+// might be supported by a native representation. For example, in
+// scripting languages like JS a struct is represented as an
+// object. The details of that representation are described together
+// with the proto support for the language.
+//
+// The JSON representation for `Struct` is JSON object.
+message Struct {
+  // Unordered map of dynamically typed values.
+  map<string, Value> fields = 1;
+}
+
+// `Value` represents a dynamically typed value which can be either
+// null, a number, a string, a boolean, a recursive struct value, or a
+// list of values. A producer of value is expected to set one of these
+// variants. Absence of any variant indicates an error.
+//
+// The JSON representation for `Value` is JSON value.
+message Value {
+  // The kind of value.
+  oneof kind {
+    // Represents a null value.
+    NullValue null_value = 1;
+    // Represents a double value.
+    double number_value = 2;
+    // Represents a string value.
+    string string_value = 3;
+    // Represents a boolean value.
+    bool bool_value = 4;
+    // Represents a structured value.
+    Struct struct_value = 5;
+    // Represents a repeated `Value`.
+    ListValue list_value = 6;
+  }
+}
+
+// `NullValue` is a singleton enumeration to represent the null value for the
+// `Value` type union.
+//
+//  The JSON representation for `NullValue` is JSON `null`.
+enum NullValue {
+  // Null value.
+  NULL_VALUE = 0;
+}
+
+// `ListValue` is a wrapper around a repeated field of values.
+//
+// The JSON representation for `ListValue` is JSON array.
+message ListValue {
+  // Repeated field of dynamically typed values.
+  repeated Value values = 1;
+}
diff --git a/src/proto/google/protobuf/timestamp.proto b/src/proto/google/protobuf/timestamp.proto
new file mode 100644
index 0000000..3b2df6d
--- /dev/null
+++ b/src/proto/google/protobuf/timestamp.proto
@@ -0,0 +1,147 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/timestamppb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TimestampProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Timestamp represents a point in time independent of any time zone or local
+// calendar, encoded as a count of seconds and fractions of seconds at
+// nanosecond resolution. The count is relative to an epoch at UTC midnight on
+// January 1, 1970, in the proleptic Gregorian calendar which extends the
+// Gregorian calendar backwards to year one.
+//
+// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+// second table is needed for interpretation, using a [24-hour linear
+// smear](https://developers.google.com/time/smear).
+//
+// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+// restricting to that range, we ensure that we can convert to and from [RFC
+// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+//
+// # Examples
+//
+// Example 1: Compute Timestamp from POSIX `time()`.
+//
+//     Timestamp timestamp;
+//     timestamp.set_seconds(time(NULL));
+//     timestamp.set_nanos(0);
+//
+// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+//
+//     struct timeval tv;
+//     gettimeofday(&tv, NULL);
+//
+//     Timestamp timestamp;
+//     timestamp.set_seconds(tv.tv_sec);
+//     timestamp.set_nanos(tv.tv_usec * 1000);
+//
+// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+//
+//     FILETIME ft;
+//     GetSystemTimeAsFileTime(&ft);
+//     UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+//
+//     // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+//     // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+//     Timestamp timestamp;
+//     timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+//     timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+//
+// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+//
+//     long millis = System.currentTimeMillis();
+//
+//     Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+//         .setNanos((int) ((millis % 1000) * 1000000)).build();
+//
+//
+// Example 5: Compute Timestamp from Java `Instant.now()`.
+//
+//     Instant now = Instant.now();
+//
+//     Timestamp timestamp =
+//         Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+//             .setNanos(now.getNano()).build();
+//
+//
+// Example 6: Compute Timestamp from current time in Python.
+//
+//     timestamp = Timestamp()
+//     timestamp.GetCurrentTime()
+//
+// # JSON Mapping
+//
+// In JSON format, the Timestamp type is encoded as a string in the
+// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+// where {year} is always expressed using four digits while {month}, {day},
+// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+// is required. A proto3 JSON serializer should always use UTC (as indicated by
+// "Z") when printing the Timestamp type and a proto3 JSON parser should be
+// able to accept both UTC and other timezones (as indicated by an offset).
+//
+// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+// 01:30 UTC on January 15, 2017.
+//
+// In JavaScript, one can convert a Date object to this format using the
+// standard
+// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+// method. In Python, a standard `datetime.datetime` object can be converted
+// to this format using
+// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+// the Joda Time's [`ISODateTimeFormat.dateTime()`](
+// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+// ) to obtain a formatter capable of generating timestamps in this format.
+//
+//
+message Timestamp {
+  // Represents seconds of UTC time since Unix epoch
+  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+  // 9999-12-31T23:59:59Z inclusive.
+  int64 seconds = 1;
+
+  // Non-negative fractions of a second at nanosecond resolution. Negative
+  // second values with fractions must still have non-negative nanos values
+  // that count forward in time. Must be from 0 to 999,999,999
+  // inclusive.
+  int32 nanos = 2;
+}
diff --git a/src/proto/google/protobuf/type.proto b/src/proto/google/protobuf/type.proto
new file mode 100644
index 0000000..d3f6a68
--- /dev/null
+++ b/src/proto/google/protobuf/type.proto
@@ -0,0 +1,187 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/source_context.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TypeProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/typepb";
+
+// A protocol buffer message type.
+message Type {
+  // The fully qualified message name.
+  string name = 1;
+  // The list of fields.
+  repeated Field fields = 2;
+  // The list of types appearing in `oneof` definitions in this type.
+  repeated string oneofs = 3;
+  // The protocol buffer options.
+  repeated Option options = 4;
+  // The source context.
+  SourceContext source_context = 5;
+  // The source syntax.
+  Syntax syntax = 6;
+}
+
+// A single field of a message type.
+message Field {
+  // Basic field types.
+  enum Kind {
+    // Field type unknown.
+    TYPE_UNKNOWN = 0;
+    // Field type double.
+    TYPE_DOUBLE = 1;
+    // Field type float.
+    TYPE_FLOAT = 2;
+    // Field type int64.
+    TYPE_INT64 = 3;
+    // Field type uint64.
+    TYPE_UINT64 = 4;
+    // Field type int32.
+    TYPE_INT32 = 5;
+    // Field type fixed64.
+    TYPE_FIXED64 = 6;
+    // Field type fixed32.
+    TYPE_FIXED32 = 7;
+    // Field type bool.
+    TYPE_BOOL = 8;
+    // Field type string.
+    TYPE_STRING = 9;
+    // Field type group. Proto2 syntax only, and deprecated.
+    TYPE_GROUP = 10;
+    // Field type message.
+    TYPE_MESSAGE = 11;
+    // Field type bytes.
+    TYPE_BYTES = 12;
+    // Field type uint32.
+    TYPE_UINT32 = 13;
+    // Field type enum.
+    TYPE_ENUM = 14;
+    // Field type sfixed32.
+    TYPE_SFIXED32 = 15;
+    // Field type sfixed64.
+    TYPE_SFIXED64 = 16;
+    // Field type sint32.
+    TYPE_SINT32 = 17;
+    // Field type sint64.
+    TYPE_SINT64 = 18;
+  }
+
+  // Whether a field is optional, required, or repeated.
+  enum Cardinality {
+    // For fields with unknown cardinality.
+    CARDINALITY_UNKNOWN = 0;
+    // For optional fields.
+    CARDINALITY_OPTIONAL = 1;
+    // For required fields. Proto2 syntax only.
+    CARDINALITY_REQUIRED = 2;
+    // For repeated fields.
+    CARDINALITY_REPEATED = 3;
+  }
+
+  // The field type.
+  Kind kind = 1;
+  // The field cardinality.
+  Cardinality cardinality = 2;
+  // The field number.
+  int32 number = 3;
+  // The field name.
+  string name = 4;
+  // The field type URL, without the scheme, for message or enumeration
+  // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
+  string type_url = 6;
+  // The index of the field type in `Type.oneofs`, for message or enumeration
+  // types. The first type has index 1; zero means the type is not in the list.
+  int32 oneof_index = 7;
+  // Whether to use alternative packed wire representation.
+  bool packed = 8;
+  // The protocol buffer options.
+  repeated Option options = 9;
+  // The field JSON name.
+  string json_name = 10;
+  // The string value of the default value of this field. Proto2 syntax only.
+  string default_value = 11;
+}
+
+// Enum type definition.
+message Enum {
+  // Enum type name.
+  string name = 1;
+  // Enum value definitions.
+  repeated EnumValue enumvalue = 2;
+  // Protocol buffer options.
+  repeated Option options = 3;
+  // The source context.
+  SourceContext source_context = 4;
+  // The source syntax.
+  Syntax syntax = 5;
+}
+
+// Enum value definition.
+message EnumValue {
+  // Enum value name.
+  string name = 1;
+  // Enum value number.
+  int32 number = 2;
+  // Protocol buffer options.
+  repeated Option options = 3;
+}
+
+// A protocol buffer option, which can be attached to a message, field,
+// enumeration, etc.
+message Option {
+  // The option's name. For protobuf built-in options (options defined in
+  // descriptor.proto), this is the short name. For example, `"map_entry"`.
+  // For custom options, it should be the fully-qualified name. For example,
+  // `"google.api.http"`.
+  string name = 1;
+  // The option's value packed in an Any message. If the value is a primitive,
+  // the corresponding wrapper type defined in google/protobuf/wrappers.proto
+  // should be used. If the value is an enum, it should be stored as an int32
+  // value using the google.protobuf.Int32Value type.
+  Any value = 2;
+}
+
+// The syntax in which a protocol buffer element is defined.
+enum Syntax {
+  // Syntax `proto2`.
+  SYNTAX_PROTO2 = 0;
+  // Syntax `proto3`.
+  SYNTAX_PROTO3 = 1;
+}
diff --git a/src/proto/google/protobuf/wrappers.proto b/src/proto/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..d49dd53
--- /dev/null
+++ b/src/proto/google/protobuf/wrappers.proto
@@ -0,0 +1,123 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+//
+// These wrappers have no meaningful use within repeated fields as they lack
+// the ability to detect presence on individual elements.
+// These wrappers have no meaningful use within a map or a oneof since
+// individual entries of a map or fields of a oneof can already detect presence.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/wrapperspb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+  // The double value.
+  double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+  // The float value.
+  float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+  // The int64 value.
+  int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+  // The uint64 value.
+  uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+  // The int32 value.
+  int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+  // The uint32 value.
+  uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+  // The bool value.
+  bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+  // The string value.
+  string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+  // The bytes value.
+  bytes value = 1;
+}
diff --git a/src/proto/mod.rs b/src/proto/mod.rs
new file mode 100644
index 0000000..8873f38
--- /dev/null
+++ b/src/proto/mod.rs
@@ -0,0 +1,18 @@
+//! This folder contains copy of .proto files
+//! needed for pure codegen.
+//!
+//! Files are copied here because when publishing to crates,
+//! referencing files from outside is not allowed.
+
+pub(crate) const RUSTPROTO_PROTO: &str = include_str!("rustproto.proto");
+pub(crate) const ANY_PROTO: &str = include_str!("google/protobuf/any.proto");
+pub(crate) const API_PROTO: &str = include_str!("google/protobuf/api.proto");
+pub(crate) const DESCRIPTOR_PROTO: &str = include_str!("google/protobuf/descriptor.proto");
+pub(crate) const DURATION_PROTO: &str = include_str!("google/protobuf/duration.proto");
+pub(crate) const EMPTY_PROTO: &str = include_str!("google/protobuf/empty.proto");
+pub(crate) const FIELD_MASK_PROTO: &str = include_str!("google/protobuf/field_mask.proto");
+pub(crate) const SOURCE_CONTEXT_PROTO: &str = include_str!("google/protobuf/source_context.proto");
+pub(crate) const STRUCT_PROTO: &str = include_str!("google/protobuf/struct.proto");
+pub(crate) const TIMESTAMP_PROTO: &str = include_str!("google/protobuf/timestamp.proto");
+pub(crate) const TYPE_PROTO: &str = include_str!("google/protobuf/type.proto");
+pub(crate) const WRAPPERS_PROTO: &str = include_str!("google/protobuf/wrappers.proto");
diff --git a/src/proto/rustproto.proto b/src/proto/rustproto.proto
new file mode 100644
index 0000000..89f0377
--- /dev/null
+++ b/src/proto/rustproto.proto
@@ -0,0 +1,47 @@
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+// see https://github.com/gogo/protobuf/blob/master/gogoproto/gogo.proto
+// for the original idea
+
+// Generated files can be customized using this proto
+// or using `Customize` struct when codegen is invoked programmatically.
+
+package rustproto;
+
+extend google.protobuf.FileOptions {
+    // When false, `get_`, `set_`, `mut_` etc. accessors are not generated
+    optional bool generate_accessors_all = 17004;
+    // When false, `get_` is not generated even if `syntax = "proto2"`
+    optional bool generate_getter_all = 17005;
+    // Use `bytes::Bytes` for `bytes` fields
+    optional bool tokio_bytes_all = 17011;
+    // Use `bytes::Bytes` for `string` fields
+    optional bool tokio_bytes_for_string_all = 17012;
+
+    // When true, will only generate codes that works with lite runtime.
+    optional bool lite_runtime_all = 17035;
+}
+
+extend google.protobuf.MessageOptions {
+    // When false, `get_`, `set_`, `mut_` etc. accessors are not generated
+    optional bool generate_accessors = 17004;
+    // When false, `get_` is not generated even if `syntax = "proto2"`
+    optional bool generate_getter = 17005;
+    // Use `bytes::Bytes` for `bytes` fields
+    optional bool tokio_bytes = 17011;
+    // Use `bytes::Bytes` for `string` fields
+    optional bool tokio_bytes_for_string = 17012;
+}
+
+extend google.protobuf.FieldOptions {
+    // When false, `get_`, `set_`, `mut_` etc. accessors are not generated
+    optional bool generate_accessors_field = 17004;
+    // When false, `get_` is not generated even if `syntax = "proto2"`
+    optional bool generate_getter_field = 17005;
+    // Use `bytes::Bytes` for `bytes` fields
+    optional bool tokio_bytes_field = 17011;
+    // Use `bytes::Bytes` for `string` fields
+    optional bool tokio_bytes_for_string_field = 17012;
+}
diff --git a/src/proto_path.rs b/src/proto_path.rs
new file mode 100644
index 0000000..dfc995e
--- /dev/null
+++ b/src/proto_path.rs
@@ -0,0 +1,158 @@
+#![doc(hidden)]
+
+use std::borrow::Borrow;
+use std::fmt;
+use std::hash::Hash;
+use std::ops::Deref;
+use std::path::Component;
+use std::path::Path;
+use std::path::PathBuf;
+
+#[derive(Debug, thiserror::Error)]
+enum Error {
+    #[error("path is empty")]
+    Empty,
+    #[error("backslashes in path: {0:?}")]
+    Backslashes(String),
+    #[error("path contains empty components: {0:?}")]
+    EmptyComponent(String),
+    #[error("dot in path: {0:?}")]
+    Dot(String),
+    #[error("dot-dot in path: {0:?}")]
+    DotDot(String),
+    #[error("path is absolute: `{}`", _0.display())]
+    Absolute(PathBuf),
+    #[error("non-UTF-8 component in path: `{}`", _0.display())]
+    NotUtf8(PathBuf),
+}
+
+/// Protobuf file relative normalized file path.
+#[repr(transparent)]
+#[derive(Eq, PartialEq, Hash, Debug)]
+pub struct ProtoPath {
+    path: str,
+}
+
+/// Protobuf file relative normalized file path.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct ProtoPathBuf {
+    path: String,
+}
+
+impl Hash for ProtoPathBuf {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.as_path().hash(state);
+    }
+}
+
+impl Borrow<ProtoPath> for ProtoPathBuf {
+    fn borrow(&self) -> &ProtoPath {
+        self.as_path()
+    }
+}
+
+impl Deref for ProtoPathBuf {
+    type Target = ProtoPath;
+
+    fn deref(&self) -> &ProtoPath {
+        self.as_path()
+    }
+}
+
+impl fmt::Display for ProtoPath {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", &self.path)
+    }
+}
+
+impl fmt::Display for ProtoPathBuf {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", &self.path)
+    }
+}
+
+impl PartialEq<str> for ProtoPath {
+    fn eq(&self, other: &str) -> bool {
+        &self.path == other
+    }
+}
+
+impl PartialEq<str> for ProtoPathBuf {
+    fn eq(&self, other: &str) -> bool {
+        &self.path == other
+    }
+}
+
+impl ProtoPath {
+    fn unchecked_new(path: &str) -> &ProtoPath {
+        unsafe { &*(path as *const str as *const ProtoPath) }
+    }
+
+    pub fn new(path: &str) -> anyhow::Result<&ProtoPath> {
+        if path.is_empty() {
+            return Err(Error::Empty.into());
+        }
+        if path.contains('\\') {
+            return Err(Error::Backslashes(path.to_owned()).into());
+        }
+        for component in path.split('/') {
+            if component.is_empty() {
+                return Err(Error::EmptyComponent(path.to_owned()).into());
+            }
+            if component == "." {
+                return Err(Error::Dot(path.to_owned()).into());
+            }
+            if component == ".." {
+                return Err(Error::DotDot(path.to_owned()).into());
+            }
+        }
+        Ok(Self::unchecked_new(path))
+    }
+
+    pub fn to_str(&self) -> &str {
+        &self.path
+    }
+
+    pub fn to_path(&self) -> &Path {
+        Path::new(&self.path)
+    }
+
+    pub fn to_proto_path_buf(&self) -> ProtoPathBuf {
+        ProtoPathBuf {
+            path: self.path.to_owned(),
+        }
+    }
+}
+
+impl ProtoPathBuf {
+    pub fn as_path(&self) -> &ProtoPath {
+        ProtoPath::unchecked_new(&self.path)
+    }
+
+    pub fn new(path: String) -> anyhow::Result<ProtoPathBuf> {
+        ProtoPath::new(&path)?;
+        Ok(ProtoPathBuf { path })
+    }
+
+    pub fn from_path(path: &Path) -> anyhow::Result<ProtoPathBuf> {
+        let mut path_str = String::new();
+        for component in path.components() {
+            match component {
+                Component::Prefix(..) => return Err(Error::Absolute(path.to_owned()).into()),
+                Component::RootDir => return Err(Error::Absolute(path.to_owned()).into()),
+                Component::CurDir if path_str.is_empty() => {}
+                Component::CurDir => return Err(Error::Dot(path.display().to_string()).into()),
+                Component::ParentDir => {
+                    return Err(Error::DotDot(path.display().to_string()).into())
+                }
+                Component::Normal(c) => {
+                    if !path_str.is_empty() {
+                        path_str.push('/');
+                    }
+                    path_str.push_str(c.to_str().ok_or_else(|| Error::NotUtf8(path.to_owned()))?);
+                }
+            }
+        }
+        Ok(ProtoPathBuf { path: path_str })
+    }
+}
diff --git a/src/protobuf_abs_path.rs b/src/protobuf_abs_path.rs
new file mode 100644
index 0000000..f678953
--- /dev/null
+++ b/src/protobuf_abs_path.rs
@@ -0,0 +1,338 @@
+#![doc(hidden)]
+
+use std::fmt;
+use std::mem;
+use std::ops::Deref;
+
+use protobuf::descriptor::FileDescriptorProto;
+use protobuf::reflect::FileDescriptor;
+use protobuf::reflect::MessageDescriptor;
+
+use crate::protobuf_ident::ProtobufIdent;
+use crate::protobuf_rel_path::ProtobufRelPath;
+use crate::ProtobufIdentRef;
+use crate::ProtobufRelPathRef;
+
+/// Protobuf absolute name (e. g. `.foo.Bar`).
+#[derive(Clone, Eq, PartialEq, Debug, Hash)]
+#[doc(hidden)]
+pub struct ProtobufAbsPath {
+    pub path: String,
+}
+
+#[doc(hidden)]
+#[derive(Eq, PartialEq, Debug, Hash)]
+#[repr(C)]
+pub struct ProtobufAbsPathRef(str);
+
+impl Default for ProtobufAbsPath {
+    fn default() -> ProtobufAbsPath {
+        ProtobufAbsPath::root()
+    }
+}
+
+impl Deref for ProtobufAbsPathRef {
+    type Target = str;
+
+    fn deref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl Deref for ProtobufAbsPath {
+    type Target = ProtobufAbsPathRef;
+
+    fn deref(&self) -> &ProtobufAbsPathRef {
+        ProtobufAbsPathRef::new(&self.path)
+    }
+}
+
+impl ProtobufAbsPathRef {
+    pub fn is_root(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    pub fn root() -> &'static ProtobufAbsPathRef {
+        Self::new("")
+    }
+
+    pub fn new(path: &str) -> &ProtobufAbsPathRef {
+        assert!(ProtobufAbsPath::is_abs(path), "{:?} is not absolute", path);
+        // SAFETY: repr(transparent)
+        unsafe { mem::transmute(path) }
+    }
+
+    pub fn remove_prefix(&self, prefix: &ProtobufAbsPathRef) -> Option<&ProtobufRelPathRef> {
+        if self.0.starts_with(&prefix.0) {
+            let rem = &self.0[prefix.0.len()..];
+            if rem.is_empty() {
+                return Some(ProtobufRelPathRef::empty());
+            }
+            if rem.starts_with('.') {
+                return Some(ProtobufRelPathRef::new(&rem[1..]));
+            }
+        }
+        None
+    }
+
+    pub fn starts_with(&self, that: &ProtobufAbsPathRef) -> bool {
+        self.remove_prefix(that).is_some()
+    }
+
+    pub fn as_str(&self) -> &str {
+        &self.0
+    }
+
+    pub fn to_owned(&self) -> ProtobufAbsPath {
+        ProtobufAbsPath {
+            path: self.0.to_owned(),
+        }
+    }
+
+    pub fn parent(&self) -> Option<&ProtobufAbsPathRef> {
+        match self.0.rfind('.') {
+            Some(pos) => Some(ProtobufAbsPathRef::new(&self.0[..pos])),
+            None => {
+                if self.0.is_empty() {
+                    None
+                } else {
+                    Some(ProtobufAbsPathRef::root())
+                }
+            }
+        }
+    }
+
+    pub fn self_and_parents(&self) -> Vec<&ProtobufAbsPathRef> {
+        let mut tmp = self;
+
+        let mut r: Vec<&ProtobufAbsPathRef> = Vec::new();
+
+        r.push(&self);
+
+        while let Some(parent) = tmp.parent() {
+            r.push(parent);
+            tmp = parent;
+        }
+
+        r
+    }
+}
+
+impl ProtobufAbsPath {
+    pub fn root() -> ProtobufAbsPath {
+        ProtobufAbsPathRef::root().to_owned()
+    }
+
+    pub fn as_ref(&self) -> &ProtobufAbsPathRef {
+        ProtobufAbsPathRef::new(&self.path)
+    }
+
+    /// If given name is an fully quialified protobuf name.
+    pub fn is_abs(path: &str) -> bool {
+        path.is_empty() || (path.starts_with(".") && path != ".")
+    }
+
+    pub fn try_new(path: &str) -> Option<ProtobufAbsPath> {
+        if ProtobufAbsPath::is_abs(path) {
+            Some(ProtobufAbsPath::new(path))
+        } else {
+            None
+        }
+    }
+
+    pub fn new<S: Into<String>>(path: S) -> ProtobufAbsPath {
+        let path = path.into();
+        assert!(
+            ProtobufAbsPath::is_abs(&path),
+            "path is not absolute: `{}`",
+            path
+        );
+        assert!(!path.ends_with("."), "{}", path);
+        ProtobufAbsPath { path }
+    }
+
+    pub fn new_from_rel(path: &str) -> ProtobufAbsPath {
+        assert!(
+            !path.starts_with("."),
+            "rel path must not start with dot: {:?}",
+            path
+        );
+        ProtobufAbsPath {
+            path: if path.is_empty() {
+                String::new()
+            } else {
+                format!(".{}", path)
+            },
+        }
+    }
+
+    pub fn package_from_file_proto(file: &FileDescriptorProto) -> ProtobufAbsPath {
+        Self::new_from_rel(file.package())
+    }
+
+    pub fn package_from_file_descriptor(file: &FileDescriptor) -> ProtobufAbsPath {
+        Self::package_from_file_proto(file.proto())
+    }
+
+    pub fn from_message(message: &MessageDescriptor) -> ProtobufAbsPath {
+        Self::new_from_rel(&message.full_name())
+    }
+
+    pub fn concat(a: &ProtobufAbsPathRef, b: &ProtobufRelPathRef) -> ProtobufAbsPath {
+        let mut a = a.to_owned();
+        a.push_relative(b);
+        a
+    }
+
+    pub fn from_path_without_dot(path: &str) -> ProtobufAbsPath {
+        assert!(!path.is_empty());
+        assert!(!path.starts_with("."));
+        assert!(!path.ends_with("."));
+        ProtobufAbsPath::new(format!(".{}", path))
+    }
+
+    pub fn from_path_maybe_dot(path: &str) -> ProtobufAbsPath {
+        if path.starts_with(".") {
+            ProtobufAbsPath::new(path.to_owned())
+        } else {
+            ProtobufAbsPath::from_path_without_dot(path)
+        }
+    }
+
+    pub fn push_simple(&mut self, simple: &ProtobufIdentRef) {
+        self.path.push('.');
+        self.path.push_str(&simple);
+    }
+
+    pub fn push_relative(&mut self, relative: &ProtobufRelPathRef) {
+        if !relative.is_empty() {
+            self.path.push_str(&format!(".{}", relative));
+        }
+    }
+
+    pub fn remove_suffix(&self, suffix: &ProtobufRelPathRef) -> Option<&ProtobufAbsPathRef> {
+        if suffix.is_empty() {
+            return Some(ProtobufAbsPathRef::new(&self.path));
+        }
+
+        if self.path.ends_with(suffix.as_str()) {
+            let rem = &self.path[..self.path.len() - suffix.as_str().len()];
+            if rem.is_empty() {
+                return Some(ProtobufAbsPathRef::root());
+            }
+            if rem.ends_with('.') {
+                return Some(ProtobufAbsPathRef::new(&rem[..rem.len() - 1]));
+            }
+        }
+        None
+    }
+
+    /// Pop the last name component
+    pub fn pop(&mut self) -> Option<ProtobufIdent> {
+        match self.path.rfind('.') {
+            Some(dot) => {
+                let ident = ProtobufIdent::new(&self.path[dot + 1..]);
+                self.path.truncate(dot);
+                Some(ident)
+            }
+            None => None,
+        }
+    }
+
+    pub fn to_root_rel(&self) -> ProtobufRelPath {
+        if self == &Self::root() {
+            ProtobufRelPath::empty()
+        } else {
+            ProtobufRelPath::new(&self.path[1..])
+        }
+    }
+
+    pub fn ends_with(&self, that: &ProtobufRelPath) -> bool {
+        self.remove_suffix(that).is_some()
+    }
+}
+
+impl From<&'_ str> for ProtobufAbsPath {
+    fn from(s: &str) -> Self {
+        ProtobufAbsPath::new(s.to_owned())
+    }
+}
+
+impl From<String> for ProtobufAbsPath {
+    fn from(s: String) -> Self {
+        ProtobufAbsPath::new(s)
+    }
+}
+
+impl fmt::Display for ProtobufAbsPathRef {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", &self.0)
+    }
+}
+
+impl fmt::Display for ProtobufAbsPath {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", ProtobufAbsPathRef::new(&self.0))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn absolute_path_push_simple() {
+        let mut foo = ProtobufAbsPath::new(".foo".to_owned());
+        foo.push_simple(ProtobufIdentRef::new("bar"));
+        assert_eq!(ProtobufAbsPath::new(".foo.bar".to_owned()), foo);
+
+        let mut foo = ProtobufAbsPath::root();
+        foo.push_simple(ProtobufIdentRef::new("bar"));
+        assert_eq!(ProtobufAbsPath::new(".bar".to_owned()), foo);
+    }
+
+    #[test]
+    fn absolute_path_remove_prefix() {
+        assert_eq!(
+            Some(ProtobufRelPathRef::empty()),
+            ProtobufAbsPath::new(".foo".to_owned())
+                .remove_prefix(&ProtobufAbsPath::new(".foo".to_owned()))
+        );
+        assert_eq!(
+            Some(ProtobufRelPathRef::new("bar")),
+            ProtobufAbsPath::new(".foo.bar".to_owned())
+                .remove_prefix(&ProtobufAbsPath::new(".foo".to_owned()))
+        );
+        assert_eq!(
+            Some(ProtobufRelPathRef::new("baz.qux")),
+            ProtobufAbsPath::new(".foo.bar.baz.qux".to_owned())
+                .remove_prefix(&ProtobufAbsPath::new(".foo.bar".to_owned()))
+        );
+        assert_eq!(
+            None,
+            ProtobufAbsPath::new(".foo.barbaz".to_owned())
+                .remove_prefix(ProtobufAbsPathRef::new(".foo.bar"))
+        );
+    }
+
+    #[test]
+    fn self_and_parents() {
+        assert_eq!(
+            vec![
+                ProtobufAbsPathRef::new(".ab.cde.fghi"),
+                ProtobufAbsPathRef::new(".ab.cde"),
+                ProtobufAbsPathRef::new(".ab"),
+                ProtobufAbsPathRef::root(),
+            ],
+            ProtobufAbsPath::new(".ab.cde.fghi".to_owned()).self_and_parents()
+        );
+    }
+
+    #[test]
+    fn ends_with() {
+        assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("")));
+        assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("bar")));
+        assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("foo.bar")));
+        assert!(!ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("foo.bar.baz")));
+    }
+}
diff --git a/src/protobuf_ident.rs b/src/protobuf_ident.rs
new file mode 100644
index 0000000..2460882
--- /dev/null
+++ b/src/protobuf_ident.rs
@@ -0,0 +1,95 @@
+#![doc(hidden)]
+
+use std::fmt;
+use std::mem;
+use std::ops::Deref;
+
+/// Identifier in `.proto` file
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
+#[doc(hidden)]
+pub struct ProtobufIdent(String);
+
+#[derive(Eq, PartialEq, Debug, Hash)]
+#[doc(hidden)]
+#[repr(transparent)]
+pub struct ProtobufIdentRef(str);
+
+impl Deref for ProtobufIdentRef {
+    type Target = str;
+
+    fn deref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl Deref for ProtobufIdent {
+    type Target = ProtobufIdentRef;
+
+    fn deref(&self) -> &ProtobufIdentRef {
+        ProtobufIdentRef::new(&self.0)
+    }
+}
+
+impl From<&'_ str> for ProtobufIdent {
+    fn from(s: &str) -> Self {
+        ProtobufIdent::new(s)
+    }
+}
+
+impl From<String> for ProtobufIdent {
+    fn from(s: String) -> Self {
+        ProtobufIdent::new(&s)
+    }
+}
+
+impl Into<String> for ProtobufIdent {
+    fn into(self) -> String {
+        self.0
+    }
+}
+
+impl fmt::Display for ProtobufIdent {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(&self.get(), f)
+    }
+}
+
+impl ProtobufIdentRef {
+    pub fn new<'a>(ident: &'a str) -> &'a ProtobufIdentRef {
+        assert!(!ident.is_empty());
+        // SAFETY: ProtobufIdentRef is repr(transparent)
+        unsafe { mem::transmute(ident) }
+    }
+
+    pub fn as_str(&self) -> &str {
+        &*self
+    }
+
+    pub fn to_owned(&self) -> ProtobufIdent {
+        ProtobufIdent(self.0.to_owned())
+    }
+}
+
+impl ProtobufIdent {
+    pub fn as_ref(&self) -> &ProtobufIdentRef {
+        ProtobufIdentRef::new(&self.0)
+    }
+
+    pub fn new(s: &str) -> ProtobufIdent {
+        assert!(!s.is_empty());
+        assert!(!s.contains("/"));
+        assert!(!s.contains("."));
+        assert!(!s.contains(":"));
+        assert!(!s.contains("("));
+        assert!(!s.contains(")"));
+        ProtobufIdent(s.to_owned())
+    }
+
+    pub fn get(&self) -> &str {
+        &self.0
+    }
+
+    pub fn into_string(self) -> String {
+        self.0
+    }
+}
diff --git a/src/protobuf_path.rs b/src/protobuf_path.rs
new file mode 100644
index 0000000..905a86d
--- /dev/null
+++ b/src/protobuf_path.rs
@@ -0,0 +1,42 @@
+use std::fmt;
+
+use crate::protobuf_abs_path::ProtobufAbsPath;
+use crate::protobuf_rel_path::ProtobufRelPath;
+
+/// Protobuf identifier can be absolute or relative.
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+pub(crate) enum ProtobufPath {
+    Abs(ProtobufAbsPath),
+    Rel(ProtobufRelPath),
+}
+
+impl ProtobufPath {
+    pub fn new<S: Into<String>>(path: S) -> ProtobufPath {
+        let path = path.into();
+        if path.starts_with('.') {
+            ProtobufPath::Abs(ProtobufAbsPath::new(path))
+        } else {
+            ProtobufPath::Rel(ProtobufRelPath::new(path))
+        }
+    }
+
+    pub fn _resolve(&self, package: &ProtobufAbsPath) -> ProtobufAbsPath {
+        match self {
+            ProtobufPath::Abs(p) => p.clone(),
+            ProtobufPath::Rel(p) => {
+                let mut package = package.clone();
+                package.push_relative(p);
+                package
+            }
+        }
+    }
+}
+
+impl fmt::Display for ProtobufPath {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProtobufPath::Abs(p) => write!(f, "{}", p),
+            ProtobufPath::Rel(p) => write!(f, "{}", p),
+        }
+    }
+}
diff --git a/src/protobuf_rel_path.rs b/src/protobuf_rel_path.rs
new file mode 100644
index 0000000..2946f49
--- /dev/null
+++ b/src/protobuf_rel_path.rs
@@ -0,0 +1,246 @@
+#![doc(hidden)]
+
+use std::fmt;
+use std::iter;
+use std::mem;
+use std::ops::Deref;
+
+use crate::protobuf_abs_path::ProtobufAbsPath;
+use crate::protobuf_ident::ProtobufIdent;
+use crate::ProtobufIdentRef;
+
+impl From<String> for ProtobufRelPath {
+    fn from(s: String) -> ProtobufRelPath {
+        ProtobufRelPath::new(s)
+    }
+}
+
+impl From<&'_ str> for ProtobufRelPath {
+    fn from(s: &str) -> ProtobufRelPath {
+        ProtobufRelPath::from(s.to_owned())
+    }
+}
+
+impl ProtobufRelPathRef {
+    pub fn as_str(&self) -> &str {
+        &self
+    }
+
+    pub fn empty() -> &'static ProtobufRelPathRef {
+        Self::new("")
+    }
+
+    pub fn new(path: &str) -> &ProtobufRelPathRef {
+        assert!(!path.starts_with('.'));
+        // SAFETY: repr(transparent)
+        unsafe { mem::transmute(path) }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    pub fn split_first_rem(&self) -> Option<(&ProtobufIdentRef, &ProtobufRelPathRef)> {
+        if self.is_empty() {
+            None
+        } else {
+            match self.0.find('.') {
+                Some(i) => Some((
+                    ProtobufIdentRef::new(&self.0[..i]),
+                    ProtobufRelPathRef::new(&self.0[i + 1..]),
+                )),
+                None => Some((ProtobufIdentRef::new(&self.0), ProtobufRelPathRef::empty())),
+            }
+        }
+    }
+
+    pub fn components(&self) -> impl Iterator<Item = &ProtobufIdentRef> {
+        iter::once(&self.0)
+            .filter(|s| !s.is_empty())
+            .flat_map(|p| p.split('.').map(|s| ProtobufIdentRef::new(s)))
+    }
+
+    fn parent(&self) -> Option<&ProtobufRelPathRef> {
+        if self.0.is_empty() {
+            None
+        } else {
+            match self.0.rfind('.') {
+                Some(i) => Some(ProtobufRelPathRef::new(&self.0[..i])),
+                None => Some(ProtobufRelPathRef::empty()),
+            }
+        }
+    }
+
+    pub fn self_and_parents(&self) -> Vec<&ProtobufRelPathRef> {
+        let mut tmp = self.clone();
+
+        let mut r = Vec::new();
+
+        r.push(self.clone());
+
+        while let Some(parent) = tmp.parent() {
+            r.push(parent);
+            tmp = parent;
+        }
+
+        r
+    }
+
+    pub fn append(&self, simple: &ProtobufRelPathRef) -> ProtobufRelPath {
+        if self.is_empty() {
+            simple.to_owned()
+        } else if simple.is_empty() {
+            self.to_owned()
+        } else {
+            ProtobufRelPath {
+                path: format!("{}.{}", &self.0, &simple.0),
+            }
+        }
+    }
+
+    pub fn append_ident(&self, simple: &ProtobufIdentRef) -> ProtobufRelPath {
+        self.append(&ProtobufRelPath::from(simple.to_owned()))
+    }
+
+    pub fn to_absolute(&self) -> ProtobufAbsPath {
+        self.to_owned().into_absolute()
+    }
+
+    pub fn to_owned(&self) -> ProtobufRelPath {
+        ProtobufRelPath {
+            path: self.0.to_owned(),
+        }
+    }
+}
+
+impl ProtobufRelPath {
+    pub fn as_ref(&self) -> &ProtobufRelPathRef {
+        &self
+    }
+
+    pub fn empty() -> ProtobufRelPath {
+        ProtobufRelPath {
+            path: String::new(),
+        }
+    }
+
+    pub fn new<S: Into<String>>(path: S) -> ProtobufRelPath {
+        let path = path.into();
+        // Validate
+        ProtobufRelPathRef::new(&path);
+        ProtobufRelPath { path }
+    }
+
+    pub fn from_components<'a, I: IntoIterator<Item = &'a ProtobufIdentRef>>(
+        i: I,
+    ) -> ProtobufRelPath {
+        let v: Vec<&str> = i.into_iter().map(|c| c.as_str()).collect();
+        ProtobufRelPath::from(v.join("."))
+    }
+
+    pub fn into_absolute(self) -> ProtobufAbsPath {
+        if self.is_empty() {
+            ProtobufAbsPath::root()
+        } else {
+            ProtobufAbsPath::from(format!(".{}", self))
+        }
+    }
+}
+
+#[doc(hidden)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+pub struct ProtobufRelPath {
+    pub(crate) path: String,
+}
+
+#[doc(hidden)]
+#[derive(Debug, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct ProtobufRelPathRef(str);
+
+impl Deref for ProtobufRelPathRef {
+    type Target = str;
+
+    fn deref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl Deref for ProtobufRelPath {
+    type Target = ProtobufRelPathRef;
+
+    fn deref(&self) -> &ProtobufRelPathRef {
+        ProtobufRelPathRef::new(&self.path)
+    }
+}
+
+impl From<ProtobufIdent> for ProtobufRelPath {
+    fn from(s: ProtobufIdent) -> ProtobufRelPath {
+        ProtobufRelPath { path: s.into() }
+    }
+}
+
+impl fmt::Display for ProtobufRelPathRef {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", &self.0)
+    }
+}
+
+impl fmt::Display for ProtobufRelPath {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.path)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn parent() {
+        assert_eq!(None, ProtobufRelPathRef::empty().parent());
+        assert_eq!(
+            Some(ProtobufRelPathRef::empty()),
+            ProtobufRelPath::new("aaa".to_owned()).parent()
+        );
+        assert_eq!(
+            Some(ProtobufRelPathRef::new("abc")),
+            ProtobufRelPath::new("abc.def".to_owned()).parent()
+        );
+        assert_eq!(
+            Some(ProtobufRelPathRef::new("abc.def")),
+            ProtobufRelPath::new("abc.def.gh".to_owned()).parent()
+        );
+    }
+
+    #[test]
+    fn self_and_parents() {
+        assert_eq!(
+            vec![
+                ProtobufRelPathRef::new("ab.cde.fghi"),
+                ProtobufRelPathRef::new("ab.cde"),
+                ProtobufRelPathRef::new("ab"),
+                ProtobufRelPathRef::empty(),
+            ],
+            ProtobufRelPath::new("ab.cde.fghi".to_owned()).self_and_parents()
+        );
+    }
+
+    #[test]
+    fn components() {
+        assert_eq!(
+            Vec::<&ProtobufIdentRef>::new(),
+            ProtobufRelPath::empty().components().collect::<Vec<_>>()
+        );
+        assert_eq!(
+            vec![ProtobufIdentRef::new("ab")],
+            ProtobufRelPath::new("ab").components().collect::<Vec<_>>()
+        );
+        assert_eq!(
+            vec![ProtobufIdentRef::new("ab"), ProtobufIdentRef::new("cd")],
+            ProtobufRelPath::new("ab.cd")
+                .components()
+                .collect::<Vec<_>>()
+        );
+    }
+}
diff --git a/src/protoc/command.rs b/src/protoc/command.rs
new file mode 100644
index 0000000..f29d7e3
--- /dev/null
+++ b/src/protoc/command.rs
@@ -0,0 +1,317 @@
+//! API to invoke `protoc` command.
+//!
+//! `protoc` command must be in `$PATH`, along with `protoc-gen-LANG` command.
+//!
+//! Note that to generate `rust` code from `.proto` files, `protoc-rust` crate
+//! can be used, which does not require `protoc-gen-rust` present in `$PATH`.
+
+#![deny(missing_docs)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::fmt;
+use std::io;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process;
+use std::process::Stdio;
+
+use log::info;
+
+#[derive(Debug, thiserror::Error)]
+enum Error {
+    #[error("protoc command exited with non-zero code")]
+    ProtocNonZero,
+    #[error("protoc command {0} exited with non-zero code")]
+    ProtocNamedNonZero(String),
+    #[error("protoc command {0} exited with non-zero code; stderr: {1:?}")]
+    ProtocNamedNonZeroStderr(String, String),
+    #[error("input is empty")]
+    InputIsEmpty,
+    #[error("output is empty")]
+    OutputIsEmpty,
+    #[error("output does not start with prefix")]
+    OutputDoesNotStartWithPrefix,
+    #[error("version is empty")]
+    VersionIsEmpty,
+    #[error("version does not start with digit")]
+    VersionDoesNotStartWithDigit,
+    #[error("failed to spawn command `{0}`")]
+    FailedToSpawnCommand(String, #[source] io::Error),
+    #[error("protoc output is not UTF-8")]
+    ProtocOutputIsNotUtf8,
+}
+
+/// `Protoc --descriptor_set_out...` args
+#[derive(Debug)]
+pub(crate) struct DescriptorSetOutArgs {
+    protoc: Protoc,
+    /// `--file_descriptor_out=...` param
+    out: Option<PathBuf>,
+    /// `-I` args
+    includes: Vec<PathBuf>,
+    /// List of `.proto` files to compile
+    inputs: Vec<PathBuf>,
+    /// `--include_imports`
+    include_imports: bool,
+    /// Extra command line flags (like `--experimental_allow_proto3_optional`)
+    extra_args: Vec<OsString>,
+    /// Capture stderr instead of inheriting it.
+    capture_stderr: bool,
+}
+
+impl DescriptorSetOutArgs {
+    /// Set `--file_descriptor_out=...` param
+    pub fn out(&mut self, out: impl AsRef<Path>) -> &mut Self {
+        self.out = Some(out.as_ref().to_owned());
+        self
+    }
+
+    /// Append a path to `-I` args
+    pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
+        self.includes.push(include.as_ref().to_owned());
+        self
+    }
+
+    /// Append multiple paths to `-I` args
+    pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
+        for include in includes {
+            self.include(include);
+        }
+        self
+    }
+
+    /// Append a `.proto` file path to compile
+    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
+        self.inputs.push(input.as_ref().to_owned());
+        self
+    }
+
+    /// Append multiple `.proto` file paths to compile
+    pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
+        for input in inputs {
+            self.input(input);
+        }
+        self
+    }
+
+    /// Set `--include_imports`
+    pub fn include_imports(&mut self, include_imports: bool) -> &mut Self {
+        self.include_imports = include_imports;
+        self
+    }
+
+    /// Add command line flags like `--experimental_allow_proto3_optional`.
+    pub fn extra_arg(&mut self, arg: impl Into<OsString>) -> &mut Self {
+        self.extra_args.push(arg.into());
+        self
+    }
+
+    /// Add command line flags like `--experimental_allow_proto3_optional`.
+    pub fn extra_args(&mut self, args: impl IntoIterator<Item = impl Into<OsString>>) -> &mut Self {
+        for arg in args {
+            self.extra_arg(arg);
+        }
+        self
+    }
+
+    /// Capture stderr instead of inheriting it.
+    pub(crate) fn capture_stderr(&mut self, capture_stderr: bool) -> &mut Self {
+        self.capture_stderr = capture_stderr;
+        self
+    }
+
+    /// Execute `protoc --descriptor_set_out=`
+    pub fn write_descriptor_set(&self) -> anyhow::Result<()> {
+        if self.inputs.is_empty() {
+            return Err(Error::InputIsEmpty.into());
+        }
+
+        let out = self.out.as_ref().ok_or_else(|| Error::OutputIsEmpty)?;
+
+        // -I{include}
+        let include_flags = self.includes.iter().map(|include| {
+            let mut flag = OsString::from("-I");
+            flag.push(include);
+            flag
+        });
+
+        // --descriptor_set_out={out}
+        let mut descriptor_set_out_flag = OsString::from("--descriptor_set_out=");
+        descriptor_set_out_flag.push(out);
+
+        // --include_imports
+        let include_imports_flag = match self.include_imports {
+            false => None,
+            true => Some("--include_imports".into()),
+        };
+
+        let mut cmd_args = Vec::new();
+        cmd_args.extend(include_flags);
+        cmd_args.push(descriptor_set_out_flag);
+        cmd_args.extend(include_imports_flag);
+        cmd_args.extend(self.inputs.iter().map(|path| path.as_os_str().to_owned()));
+        cmd_args.extend(self.extra_args.iter().cloned());
+        self.protoc.run_with_args(cmd_args, self.capture_stderr)
+    }
+}
+
+/// Protoc command.
+#[derive(Clone, Debug)]
+pub(crate) struct Protoc {
+    exec: OsString,
+}
+
+impl Protoc {
+    /// New `protoc` command from `$PATH`
+    pub(crate) fn from_env_path() -> Protoc {
+        match which::which("protoc") {
+            Ok(path) => Protoc {
+                exec: path.into_os_string(),
+            },
+            Err(e) => {
+                panic!("protoc binary not found: {}", e);
+            }
+        }
+    }
+
+    /// New `protoc` command from specified path
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// # mod protoc_bin_vendored {
+    /// #   pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> {
+    /// #       unimplemented!()
+    /// #   }
+    /// # }
+    ///
+    /// // Use a binary from `protoc-bin-vendored` crate
+    /// let protoc = protoc::Protoc::from_path(
+    ///     protoc_bin_vendored::protoc_bin_path().unwrap());
+    /// ```
+    pub(crate) fn from_path(path: impl AsRef<OsStr>) -> Protoc {
+        Protoc {
+            exec: path.as_ref().to_owned(),
+        }
+    }
+
+    /// Check `protoc` command found and valid
+    pub(crate) fn _check(&self) -> anyhow::Result<()> {
+        self.version()?;
+        Ok(())
+    }
+
+    fn spawn(&self, cmd: &mut process::Command) -> anyhow::Result<process::Child> {
+        info!("spawning command {:?}", cmd);
+
+        cmd.spawn()
+            .map_err(|e| Error::FailedToSpawnCommand(format!("{:?}", cmd), e).into())
+    }
+
+    /// Obtain `protoc` version
+    pub(crate) fn version(&self) -> anyhow::Result<Version> {
+        let child = self.spawn(
+            process::Command::new(&self.exec)
+                .stdin(process::Stdio::null())
+                .stdout(process::Stdio::piped())
+                .stderr(process::Stdio::piped())
+                .args(&["--version"]),
+        )?;
+
+        let output = child.wait_with_output()?;
+        if !output.status.success() {
+            return Err(Error::ProtocNonZero.into());
+        }
+        let output = String::from_utf8(output.stdout).map_err(|_| Error::ProtocOutputIsNotUtf8)?;
+        let output = match output.lines().next() {
+            None => return Err(Error::OutputIsEmpty.into()),
+            Some(line) => line,
+        };
+        let prefix = "libprotoc ";
+        if !output.starts_with(prefix) {
+            return Err(Error::OutputDoesNotStartWithPrefix.into());
+        }
+        let output = &output[prefix.len()..];
+        if output.is_empty() {
+            return Err(Error::VersionIsEmpty.into());
+        }
+        let first = output.chars().next().unwrap();
+        if !first.is_digit(10) {
+            return Err(Error::VersionDoesNotStartWithDigit.into());
+        }
+        Ok(Version {
+            version: output.to_owned(),
+        })
+    }
+
+    /// Execute `protoc` command with given args, check it completed correctly.
+    fn run_with_args(&self, args: Vec<OsString>, capture_stderr: bool) -> anyhow::Result<()> {
+        let mut cmd = process::Command::new(&self.exec);
+        cmd.stdin(process::Stdio::null());
+        cmd.args(args);
+
+        if capture_stderr {
+            cmd.stderr(Stdio::piped());
+        }
+
+        let mut child = self.spawn(&mut cmd)?;
+
+        if capture_stderr {
+            let output = child.wait_with_output()?;
+            if !output.status.success() {
+                let stderr = String::from_utf8_lossy(&output.stderr);
+                let stderr = stderr.trim_end().to_owned();
+                return Err(Error::ProtocNamedNonZeroStderr(format!("{:?}", cmd), stderr).into());
+            }
+        } else {
+            if !child.wait()?.success() {
+                return Err(Error::ProtocNamedNonZero(format!("{:?}", cmd)).into());
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Get default DescriptorSetOutArgs for this command.
+    pub(crate) fn descriptor_set_out_args(&self) -> DescriptorSetOutArgs {
+        DescriptorSetOutArgs {
+            protoc: self.clone(),
+            out: None,
+            includes: Vec::new(),
+            inputs: Vec::new(),
+            include_imports: false,
+            extra_args: Vec::new(),
+            capture_stderr: false,
+        }
+    }
+}
+
+/// Protobuf (protoc) version.
+pub(crate) struct Version {
+    version: String,
+}
+
+impl Version {
+    /// `true` if the protoc major version is 3.
+    pub fn _is_3(&self) -> bool {
+        self.version.starts_with("3")
+    }
+}
+
+impl fmt::Display for Version {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(&self.version, f)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn version() {
+        Protoc::from_env_path().version().expect("version");
+    }
+}
diff --git a/src/protoc/mod.rs b/src/protoc/mod.rs
new file mode 100644
index 0000000..88c9621
--- /dev/null
+++ b/src/protoc/mod.rs
@@ -0,0 +1,4 @@
+//! Parse `.proto` files using `protoc` command.
+
+pub(crate) mod command;
+pub(crate) mod parse_and_typecheck;
diff --git a/src/protoc/parse_and_typecheck.rs b/src/protoc/parse_and_typecheck.rs
new file mode 100644
index 0000000..6af70c1
--- /dev/null
+++ b/src/protoc/parse_and_typecheck.rs
@@ -0,0 +1,52 @@
+use std::fs;
+
+use protobuf::descriptor::FileDescriptorSet;
+use protobuf::Message;
+
+use crate::protoc::command::Protoc;
+use crate::pure::parse_and_typecheck::path_to_proto_path;
+use crate::ParsedAndTypechecked;
+use crate::Parser;
+use crate::ProtoPathBuf;
+
+/// Parse `.proto` files using `protoc` command.
+pub(crate) fn parse_and_typecheck(parser: &Parser) -> anyhow::Result<ParsedAndTypechecked> {
+    let temp_dir = tempfile::Builder::new()
+        .prefix("protobuf-parse")
+        .tempdir()?;
+    let temp_file = temp_dir.path().join("descriptor.pbbin");
+
+    let relative_paths: Vec<ProtoPathBuf> = parser
+        .inputs
+        .iter()
+        .map(|p| path_to_proto_path(p, &parser.includes))
+        .collect::<anyhow::Result<_>>()?;
+
+    let protoc = match &parser.protoc {
+        Some(protoc) => Protoc::from_path(protoc),
+        None => Protoc::from_env_path(),
+    };
+
+    protoc
+        .descriptor_set_out_args()
+        .inputs(&parser.inputs)
+        .includes(&parser.includes)
+        .out(&temp_file)
+        .include_imports(true)
+        .extra_args(&parser.protoc_extra_args)
+        .capture_stderr(parser.capture_stderr)
+        .write_descriptor_set()?;
+
+    let version = protoc.version()?;
+
+    let fds = fs::read(temp_file)?;
+    drop(temp_dir);
+
+    let fds: protobuf::descriptor::FileDescriptorSet = FileDescriptorSet::parse_from_bytes(&fds)?;
+
+    Ok(ParsedAndTypechecked {
+        relative_paths,
+        file_descriptors: fds.file,
+        parser: format!("protoc {}", version),
+    })
+}
diff --git a/src/pure/convert/mod.rs b/src/pure/convert/mod.rs
new file mode 100644
index 0000000..c98fcf5
--- /dev/null
+++ b/src/pure/convert/mod.rs
@@ -0,0 +1,688 @@
+//! Convert parser model to rust-protobuf model
+
+mod option_resolver;
+mod type_resolver;
+
+use protobuf;
+use protobuf::descriptor::descriptor_proto::ReservedRange;
+use protobuf::descriptor::field_descriptor_proto;
+use protobuf::descriptor::field_descriptor_proto::Type;
+use protobuf::descriptor::FieldDescriptorProto;
+use protobuf::descriptor::OneofDescriptorProto;
+use protobuf::reflect::FileDescriptor;
+use protobuf_support::json_name::json_name;
+use protobuf_support::text_format::escape_bytes_to;
+
+use crate::case_convert::camel_case;
+use crate::path::fs_path_to_proto_path;
+use crate::proto_path::ProtoPath;
+use crate::protobuf_abs_path::ProtobufAbsPath;
+use crate::protobuf_ident::ProtobufIdent;
+use crate::pure::convert::option_resolver::OptionResoler;
+use crate::pure::convert::option_resolver::ProtobufOptions;
+use crate::pure::convert::type_resolver::MessageOrEnum;
+use crate::pure::convert::type_resolver::TypeResolver;
+use crate::pure::model;
+use crate::FileDescriptorPair;
+use crate::ProtobufAbsPathRef;
+use crate::ProtobufIdentRef;
+
+#[derive(Debug, thiserror::Error)]
+enum ConvertError {
+    #[error("default value is not a string literal")]
+    DefaultValueIsNotStringLiteral,
+    #[error("expecting a message for name {0}")]
+    ExpectingMessage(ProtobufAbsPath),
+    #[error("expecting an enum for name {0}")]
+    ExpectingEnum(ProtobufAbsPath),
+}
+
+pub struct WithFullName<T> {
+    full_name: ProtobufAbsPath,
+    t: T,
+}
+
+#[derive(Debug, PartialEq)]
+enum TypeResolved {
+    Int32,
+    Int64,
+    Uint32,
+    Uint64,
+    Sint32,
+    Sint64,
+    Bool,
+    Fixed64,
+    Sfixed64,
+    Double,
+    String,
+    Bytes,
+    Fixed32,
+    Sfixed32,
+    Float,
+    Message(ProtobufAbsPath),
+    Enum(ProtobufAbsPath),
+    Group(ProtobufAbsPath),
+}
+
+impl TypeResolved {
+    fn from_field(field: &FieldDescriptorProto) -> TypeResolved {
+        match field.type_() {
+            Type::TYPE_DOUBLE => TypeResolved::Double,
+            Type::TYPE_FLOAT => TypeResolved::Float,
+            Type::TYPE_INT64 => TypeResolved::Int64,
+            Type::TYPE_UINT64 => TypeResolved::Uint64,
+            Type::TYPE_INT32 => TypeResolved::Int32,
+            Type::TYPE_FIXED64 => TypeResolved::Fixed64,
+            Type::TYPE_FIXED32 => TypeResolved::Fixed32,
+            Type::TYPE_UINT32 => TypeResolved::Uint32,
+            Type::TYPE_SFIXED32 => TypeResolved::Sfixed32,
+            Type::TYPE_SFIXED64 => TypeResolved::Sfixed64,
+            Type::TYPE_SINT32 => TypeResolved::Sint32,
+            Type::TYPE_SINT64 => TypeResolved::Sint64,
+            Type::TYPE_BOOL => TypeResolved::Bool,
+            Type::TYPE_STRING => TypeResolved::String,
+            Type::TYPE_BYTES => TypeResolved::Bytes,
+            Type::TYPE_GROUP => {
+                assert!(!field.type_name().is_empty());
+                TypeResolved::Group(ProtobufAbsPath::new(field.type_name()))
+            }
+            Type::TYPE_ENUM => {
+                assert!(!field.type_name().is_empty());
+                TypeResolved::Enum(ProtobufAbsPath::new(field.type_name()))
+            }
+            Type::TYPE_MESSAGE => {
+                assert!(!field.type_name().is_empty());
+                TypeResolved::Message(ProtobufAbsPath::new(field.type_name()))
+            }
+        }
+    }
+
+    fn type_enum(&self) -> Type {
+        match self {
+            TypeResolved::Bool => Type::TYPE_BOOL,
+            TypeResolved::Int32 => Type::TYPE_INT32,
+            TypeResolved::Int64 => Type::TYPE_INT64,
+            TypeResolved::Uint32 => Type::TYPE_UINT32,
+            TypeResolved::Uint64 => Type::TYPE_UINT64,
+            TypeResolved::Sint32 => Type::TYPE_SINT32,
+            TypeResolved::Sint64 => Type::TYPE_SINT64,
+            TypeResolved::Fixed32 => Type::TYPE_FIXED32,
+            TypeResolved::Fixed64 => Type::TYPE_FIXED64,
+            TypeResolved::Sfixed32 => Type::TYPE_SFIXED32,
+            TypeResolved::Sfixed64 => Type::TYPE_SFIXED64,
+            TypeResolved::Float => Type::TYPE_FLOAT,
+            TypeResolved::Double => Type::TYPE_DOUBLE,
+            TypeResolved::String => Type::TYPE_STRING,
+            TypeResolved::Bytes => Type::TYPE_BYTES,
+            TypeResolved::Message(_) => Type::TYPE_MESSAGE,
+            TypeResolved::Enum(_) => Type::TYPE_ENUM,
+            TypeResolved::Group(_) => Type::TYPE_GROUP,
+        }
+    }
+
+    fn type_name(&self) -> Option<&ProtobufAbsPath> {
+        match self {
+            TypeResolved::Message(t) | TypeResolved::Enum(t) | TypeResolved::Group(t) => Some(t),
+            _ => None,
+        }
+    }
+}
+
+pub(crate) struct Resolver<'a> {
+    type_resolver: TypeResolver<'a>,
+    current_file: &'a model::FileDescriptor,
+}
+
+impl<'a> Resolver<'a> {
+    fn map_entry_name_for_field_name(field_name: &str) -> ProtobufIdent {
+        // Field name and message name must match, otherwise
+        // Google's validation fails.
+        // https://git.io/JeOvF
+        ProtobufIdent::from(format!("{}Entry", camel_case(field_name)))
+    }
+
+    fn map_entry_field(
+        &self,
+        scope: &ProtobufAbsPath,
+        name: &str,
+        number: i32,
+        field_type: &model::FieldType,
+    ) -> anyhow::Result<protobuf::descriptor::FieldDescriptorProto> {
+        // should be consisent with DescriptorBuilder::ValidateMapEntry
+
+        let mut output = protobuf::descriptor::FieldDescriptorProto::new();
+
+        output.set_name(name.to_owned());
+        output.set_number(number);
+
+        let t = self.field_type(&scope, name, field_type)?;
+        output.set_type(t.type_enum());
+        if let Some(t_name) = t.type_name() {
+            output.set_type_name(t_name.path.clone());
+        }
+
+        output.set_label(field_descriptor_proto::Label::LABEL_OPTIONAL);
+
+        output.set_json_name(json_name(&name));
+
+        Ok(output)
+    }
+
+    fn map_entry_message(
+        &self,
+        scope: &ProtobufAbsPath,
+        field_name: &str,
+        key: &model::FieldType,
+        value: &model::FieldType,
+    ) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
+        let mut output = protobuf::descriptor::DescriptorProto::new();
+
+        output.options.mut_or_insert_default().set_map_entry(true);
+        output.set_name(Resolver::map_entry_name_for_field_name(field_name).into_string());
+        output
+            .field
+            .push(self.map_entry_field(&scope, "key", 1, key)?);
+        output
+            .field
+            .push(self.map_entry_field(&scope, "value", 2, value)?);
+
+        Ok(output)
+    }
+
+    fn group_message(
+        &self,
+        scope: &ProtobufAbsPath,
+        name: &str,
+        fields: &[model::WithLoc<model::Field>],
+    ) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
+        let mut output = protobuf::descriptor::DescriptorProto::new();
+
+        output.set_name(name.to_owned());
+
+        for f in fields {
+            output.field.push(self.field(scope, f, None)?);
+        }
+
+        Ok(output)
+    }
+
+    fn message(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &model::Message,
+    ) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
+        let mut nested_scope = scope.to_owned();
+        nested_scope.push_simple(ProtobufIdentRef::new(&input.name));
+
+        let mut output = protobuf::descriptor::DescriptorProto::new();
+        output.set_name(input.name.clone());
+
+        let mut nested_messages = Vec::new();
+
+        for m in &input.messages {
+            let message = self.message(&nested_scope, &m.t)?;
+            nested_messages.push(model::WithLoc {
+                t: message,
+                loc: m.loc,
+            });
+        }
+
+        for f in input.regular_fields_including_in_oneofs() {
+            match &f.t.typ {
+                model::FieldType::Map(t) => {
+                    let message = self.map_entry_message(&nested_scope, &f.t.name, &t.0, &t.1)?;
+                    nested_messages.push(model::WithLoc {
+                        t: message,
+                        loc: f.loc,
+                    });
+                }
+                model::FieldType::Group(model::Group {
+                    name: group_name,
+                    fields,
+                    ..
+                }) => {
+                    let message = self.group_message(&nested_scope, group_name, fields)?;
+                    nested_messages.push(model::WithLoc {
+                        t: message,
+                        loc: f.loc,
+                    });
+                }
+                _ => (),
+            }
+        }
+
+        // Preserve declaration order
+        nested_messages.sort_by_key(|m| m.loc);
+        output.nested_type = nested_messages
+            .into_iter()
+            .map(|model::WithLoc { t, .. }| t)
+            .collect();
+
+        output.enum_type = input
+            .enums
+            .iter()
+            .map(|e| self.enumeration(scope, e))
+            .collect::<Result<_, _>>()?;
+
+        {
+            let mut fields = Vec::new();
+
+            for fo in &input.fields {
+                match &fo.t {
+                    model::FieldOrOneOf::Field(f) => {
+                        let oneof_index = if self.is_proto3_optional(f) {
+                            let oneof_index = output.oneof_decl.len() as i32;
+                            let mut oneof = OneofDescriptorProto::new();
+                            oneof.set_name(format!("_{}", f.name));
+                            output.oneof_decl.push(oneof);
+                            Some(oneof_index)
+                        } else {
+                            None
+                        };
+                        fields.push(self.field(&nested_scope, f, oneof_index)?);
+                    }
+                    model::FieldOrOneOf::OneOf(o) => {
+                        let oneof_index = output.oneof_decl.len();
+                        for f in &o.fields {
+                            fields.push(self.field(&nested_scope, f, Some(oneof_index as i32))?);
+                        }
+                        output.oneof_decl.push(self.oneof(scope, o)?);
+                    }
+                }
+            }
+
+            output.field = fields;
+        }
+
+        for ext in &input.extension_ranges {
+            let mut extension_range = protobuf::descriptor::descriptor_proto::ExtensionRange::new();
+            extension_range.set_start(ext.from);
+            extension_range.set_end(ext.to + 1);
+            output.extension_range.push(extension_range);
+        }
+        for ext in &input.extensions {
+            let mut extension = self.field(scope, &ext.t.field, None)?;
+            extension.set_extendee(
+                self.type_resolver
+                    .resolve_message_or_enum(scope, &ext.t.extendee)?
+                    .full_name
+                    .path,
+            );
+            output.extension.push(extension);
+        }
+
+        for reserved in &input.reserved_nums {
+            let mut reserved_range = ReservedRange::new();
+            reserved_range.set_start(reserved.from);
+            reserved_range.set_end(reserved.to + 1);
+            output.reserved_range.push(reserved_range);
+        }
+        output.reserved_name = input.reserved_names.clone().into();
+
+        Ok(output)
+    }
+
+    fn service_method(
+        &self,
+        input: &model::Method,
+    ) -> anyhow::Result<protobuf::descriptor::MethodDescriptorProto> {
+        let scope = &self.current_file.package;
+        let mut output = protobuf::descriptor::MethodDescriptorProto::new();
+        output.set_name(input.name.clone());
+        output.set_input_type(
+            self.type_resolver
+                .resolve_message_or_enum(scope, &input.input_type)?
+                .full_name
+                .to_string(),
+        );
+        output.set_output_type(
+            self.type_resolver
+                .resolve_message_or_enum(scope, &input.output_type)?
+                .full_name
+                .to_string(),
+        );
+        Ok(output)
+    }
+
+    fn service(
+        &self,
+        input: &model::Service,
+    ) -> anyhow::Result<protobuf::descriptor::ServiceDescriptorProto> {
+        let mut output = protobuf::descriptor::ServiceDescriptorProto::new();
+        output.set_name(input.name.clone());
+
+        output.method = input
+            .methods
+            .iter()
+            .map(|m| self.service_method(m))
+            .collect::<Result<_, _>>()?;
+
+        Ok(output)
+    }
+
+    fn is_proto3_optional(&self, input: &model::WithLoc<model::Field>) -> bool {
+        (self.current_file.syntax, input.t.rule)
+            == (model::Syntax::Proto3, Some(model::Rule::Optional))
+    }
+
+    fn field(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &model::WithLoc<model::Field>,
+        oneof_index: Option<i32>,
+    ) -> anyhow::Result<protobuf::descriptor::FieldDescriptorProto> {
+        let mut output = protobuf::descriptor::FieldDescriptorProto::new();
+        output.set_name(input.t.name.clone());
+
+        if let model::FieldType::Map(..) = input.t.typ {
+            output.set_label(protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED);
+        } else {
+            output.set_label(label(input.t.rule));
+
+            if self.is_proto3_optional(input) {
+                output.set_proto3_optional(true);
+            }
+        }
+
+        let t = self.field_type(scope, &input.t.name, &input.t.typ)?;
+        output.set_type(t.type_enum());
+        if let Some(t_name) = t.type_name() {
+            output.set_type_name(t_name.path.clone());
+        }
+
+        output.set_number(input.t.number);
+        // TODO: move default to option parser
+        if let Some(ref default) = input.t.options.as_slice().by_name("default") {
+            let default = match output.type_() {
+                protobuf::descriptor::field_descriptor_proto::Type::TYPE_STRING => {
+                    if let &model::ProtobufConstant::String(ref s) = default {
+                        s.decode_utf8()?
+                    } else {
+                        return Err(ConvertError::DefaultValueIsNotStringLiteral.into());
+                    }
+                }
+                protobuf::descriptor::field_descriptor_proto::Type::TYPE_BYTES => {
+                    if let &model::ProtobufConstant::String(ref s) = default {
+                        let mut buf = String::new();
+                        escape_bytes_to(&s.decode_bytes()?, &mut buf);
+                        buf
+                    } else {
+                        return Err(ConvertError::DefaultValueIsNotStringLiteral.into());
+                    }
+                }
+                _ => default.format(),
+            };
+            output.set_default_value(default);
+        }
+
+        if let Some(oneof_index) = oneof_index {
+            output.set_oneof_index(oneof_index);
+        }
+
+        if let Some(json_name) = input.t.options.as_slice().by_name_string("json_name")? {
+            output.set_json_name(json_name);
+        } else {
+            output.set_json_name(json_name(&input.t.name));
+        }
+
+        Ok(output)
+    }
+
+    fn find_message_by_abs_name(
+        &self,
+        abs_path: &ProtobufAbsPath,
+    ) -> anyhow::Result<WithFullName<&'a model::Message>> {
+        let with_full_name = self
+            .type_resolver
+            .find_message_or_enum_by_abs_name(abs_path)?;
+        match with_full_name.t {
+            MessageOrEnum::Message(m) => Ok(WithFullName {
+                t: m,
+                full_name: with_full_name.full_name,
+            }),
+            MessageOrEnum::Enum(..) => Err(ConvertError::ExpectingMessage(abs_path.clone()).into()),
+        }
+    }
+
+    fn find_enum_by_abs_name(
+        &self,
+        abs_path: &ProtobufAbsPath,
+    ) -> anyhow::Result<&'a model::Enumeration> {
+        match self
+            .type_resolver
+            .find_message_or_enum_by_abs_name(abs_path)?
+            .t
+        {
+            MessageOrEnum::Enum(e) => Ok(e),
+            MessageOrEnum::Message(..) => Err(ConvertError::ExpectingEnum(abs_path.clone()).into()),
+        }
+    }
+
+    fn field_type(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        name: &str,
+        input: &model::FieldType,
+    ) -> anyhow::Result<TypeResolved> {
+        Ok(match *input {
+            model::FieldType::Bool => TypeResolved::Bool,
+            model::FieldType::Int32 => TypeResolved::Int32,
+            model::FieldType::Int64 => TypeResolved::Int64,
+            model::FieldType::Uint32 => TypeResolved::Uint32,
+            model::FieldType::Uint64 => TypeResolved::Uint64,
+            model::FieldType::Sint32 => TypeResolved::Sint32,
+            model::FieldType::Sint64 => TypeResolved::Sint64,
+            model::FieldType::Fixed32 => TypeResolved::Fixed32,
+            model::FieldType::Fixed64 => TypeResolved::Fixed64,
+            model::FieldType::Sfixed32 => TypeResolved::Sfixed32,
+            model::FieldType::Sfixed64 => TypeResolved::Sfixed64,
+            model::FieldType::Float => TypeResolved::Float,
+            model::FieldType::Double => TypeResolved::Double,
+            model::FieldType::String => TypeResolved::String,
+            model::FieldType::Bytes => TypeResolved::Bytes,
+            model::FieldType::MessageOrEnum(ref name) => {
+                let t = self.type_resolver.resolve_message_or_enum(scope, &name)?;
+                match t.t {
+                    MessageOrEnum::Message(..) => TypeResolved::Message(t.full_name),
+                    MessageOrEnum::Enum(..) => TypeResolved::Enum(t.full_name),
+                }
+            }
+            model::FieldType::Map(..) => {
+                let mut type_name = scope.to_owned();
+                type_name.push_simple(&Resolver::map_entry_name_for_field_name(name));
+                TypeResolved::Message(type_name)
+            }
+            model::FieldType::Group(model::Group {
+                name: ref group_name,
+                ..
+            }) => {
+                let mut type_name = scope.to_owned();
+                type_name.push_simple(ProtobufIdentRef::new(group_name));
+                TypeResolved::Group(type_name)
+            }
+        })
+    }
+
+    fn enum_value(
+        &self,
+        _scope: &ProtobufAbsPathRef,
+        input: &model::EnumValue,
+    ) -> anyhow::Result<protobuf::descriptor::EnumValueDescriptorProto> {
+        let mut output = protobuf::descriptor::EnumValueDescriptorProto::new();
+        output.set_name(input.name.clone());
+        output.set_number(input.number);
+        Ok(output)
+    }
+
+    fn enumeration(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &model::Enumeration,
+    ) -> anyhow::Result<protobuf::descriptor::EnumDescriptorProto> {
+        let mut output = protobuf::descriptor::EnumDescriptorProto::new();
+        output.set_name(input.name.clone());
+        output.value = input
+            .values
+            .iter()
+            .map(|v| self.enum_value(scope, &v))
+            .collect::<Result<_, _>>()?;
+        Ok(output)
+    }
+
+    fn oneof(
+        &self,
+        _scope: &ProtobufAbsPathRef,
+        input: &model::OneOf,
+    ) -> anyhow::Result<protobuf::descriptor::OneofDescriptorProto> {
+        let mut output = protobuf::descriptor::OneofDescriptorProto::new();
+        output.set_name(input.name.clone());
+        Ok(output)
+    }
+
+    fn extension(
+        &self,
+        scope: &ProtobufAbsPath,
+        input: &model::Extension,
+    ) -> anyhow::Result<(
+        protobuf::descriptor::FieldDescriptorProto,
+        Option<protobuf::descriptor::DescriptorProto>,
+    )> {
+        let mut field = self.field(scope, &input.field, None)?;
+        field.set_extendee(
+            self.type_resolver
+                .resolve_message_or_enum(scope, &input.extendee)?
+                .full_name
+                .to_string(),
+        );
+        let group_messages = if let model::FieldType::Group(g) = &input.field.t.typ {
+            Some(self.group_message(scope, &g.name, &g.fields)?)
+        } else {
+            None
+        };
+        Ok((field, group_messages))
+    }
+}
+
+fn syntax(input: model::Syntax) -> String {
+    match input {
+        model::Syntax::Proto2 => "proto2".to_owned(),
+        model::Syntax::Proto3 => "proto3".to_owned(),
+    }
+}
+
+fn label(input: Option<model::Rule>) -> protobuf::descriptor::field_descriptor_proto::Label {
+    match input {
+        Some(model::Rule::Optional) => {
+            protobuf::descriptor::field_descriptor_proto::Label::LABEL_OPTIONAL
+        }
+        Some(model::Rule::Required) => {
+            protobuf::descriptor::field_descriptor_proto::Label::LABEL_REQUIRED
+        }
+        Some(model::Rule::Repeated) => {
+            protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED
+        }
+        None => protobuf::descriptor::field_descriptor_proto::Label::LABEL_OPTIONAL,
+    }
+}
+
+pub(crate) fn populate_dependencies(
+    input: &model::FileDescriptor,
+    output: &mut protobuf::descriptor::FileDescriptorProto,
+) {
+    for import in &input.imports {
+        if import.vis == model::ImportVis::Public {
+            output
+                .public_dependency
+                .push(output.dependency.len() as i32);
+        } else if import.vis == model::ImportVis::Weak {
+            output.weak_dependency.push(output.dependency.len() as i32);
+        }
+        output.dependency.push(import.path.to_string());
+    }
+}
+
+pub(crate) fn file_descriptor(
+    name: &ProtoPath,
+    input: &model::FileDescriptor,
+    deps: &[FileDescriptorPair],
+) -> anyhow::Result<protobuf::descriptor::FileDescriptorProto> {
+    let resolver = Resolver {
+        current_file: &input,
+        type_resolver: TypeResolver {
+            current_file: &input,
+            deps,
+        },
+    };
+
+    let mut output = protobuf::descriptor::FileDescriptorProto::new();
+    output.set_name(fs_path_to_proto_path(name));
+    output.set_syntax(syntax(input.syntax));
+
+    if input.package != ProtobufAbsPath::root() {
+        output.set_package(input.package.to_root_rel().to_string());
+    }
+
+    populate_dependencies(&input, &mut output);
+
+    let mut messages = Vec::new();
+    let mut services = Vec::new();
+
+    let mut extensions = Vec::new();
+    for e in &input.extensions {
+        let (ext, group_messages) = resolver.extension(&resolver.current_file.package, &e.t)?;
+        extensions.push(ext);
+        messages.extend(group_messages.map(model::WithLoc::with_loc(e.loc)));
+    }
+    output.extension = extensions;
+
+    for m in &input.messages {
+        let message = resolver.message(&resolver.current_file.package, &m.t)?;
+        messages.push(model::WithLoc {
+            t: message,
+            loc: m.loc,
+        });
+    }
+
+    for s in &input.services {
+        let service = resolver.service(&s.t)?;
+        services.push(model::WithLoc {
+            t: service,
+            loc: s.loc,
+        })
+    }
+
+    // Preserve declaration order
+    messages.sort_by_key(|m| m.loc);
+    output.message_type = messages
+        .into_iter()
+        .map(|model::WithLoc { t, .. }| t)
+        .collect();
+
+    output.enum_type = input
+        .enums
+        .iter()
+        .map(|e| resolver.enumeration(&resolver.current_file.package, e))
+        .collect::<Result<_, _>>()?;
+
+    output.service = services
+        .into_iter()
+        .map(|model::WithLoc { t, .. }| t)
+        .collect();
+
+    let descriptor_without_options = FileDescriptor::new_dynamic(
+        output.clone(),
+        &deps
+            .iter()
+            .map(|d| d.descriptor.clone())
+            .collect::<Vec<_>>(),
+    )?;
+
+    let option_resolver = OptionResoler {
+        resolver: &resolver,
+        descriptor_without_options,
+    };
+
+    option_resolver.file(&mut output)?;
+
+    Ok(output)
+}
diff --git a/src/pure/convert/option_resolver.rs b/src/pure/convert/option_resolver.rs
new file mode 100644
index 0000000..37e1534
--- /dev/null
+++ b/src/pure/convert/option_resolver.rs
@@ -0,0 +1,1006 @@
+use anyhow::Context;
+use protobuf::descriptor::DescriptorProto;
+use protobuf::descriptor::EnumDescriptorProto;
+use protobuf::descriptor::EnumValueDescriptorProto;
+use protobuf::descriptor::FieldDescriptorProto;
+use protobuf::descriptor::FileDescriptorProto;
+use protobuf::descriptor::MethodDescriptorProto;
+use protobuf::descriptor::OneofDescriptorProto;
+use protobuf::descriptor::ServiceDescriptorProto;
+use protobuf::reflect::FieldDescriptor;
+use protobuf::reflect::FileDescriptor;
+use protobuf::reflect::MessageDescriptor;
+use protobuf::MessageFull;
+use protobuf::UnknownFields;
+use protobuf::UnknownValue;
+use protobuf_support::lexer::str_lit::StrLitDecodeError;
+
+use crate::model;
+use crate::model::ProtobufConstant;
+use crate::model::ProtobufConstantMessage;
+use crate::model::ProtobufConstantMessageFieldName;
+use crate::model::ProtobufOptionName;
+use crate::model::ProtobufOptionNameExt;
+use crate::model::ProtobufOptionNamePart;
+use crate::model::WithLoc;
+use crate::protobuf_path::ProtobufPath;
+use crate::pure::convert::Resolver;
+use crate::pure::convert::TypeResolved;
+use crate::ProtobufAbsPath;
+use crate::ProtobufAbsPathRef;
+use crate::ProtobufIdent;
+use crate::ProtobufIdentRef;
+use crate::ProtobufRelPath;
+use crate::ProtobufRelPathRef;
+
+#[derive(Debug, thiserror::Error)]
+enum OptionResolverError {
+    #[error(transparent)]
+    OtherError(anyhow::Error),
+    #[error("extension is not a message: {0}")]
+    ExtensionIsNotMessage(String),
+    #[error("unknown field name: {0}")]
+    UnknownFieldName(String),
+    #[error("wrong extension type: option {0} extendee {1} expected extendee {2}")]
+    WrongExtensionType(String, String, String),
+    #[error("extension not found: {0}")]
+    ExtensionNotFound(String),
+    #[error("unknown enum value: {0}")]
+    UnknownEnumValue(String),
+    #[error("unsupported extension type: {0} {1} {2}")]
+    UnsupportedExtensionType(String, String, model::ProtobufConstant),
+    #[error("builtin option {0} not found for options {1}")]
+    BuiltinOptionNotFound(String, String),
+    #[error("builtin option {0} points to a non-singular field of {1}")]
+    BuiltinOptionPointsToNonSingularField(String, String),
+    #[error("incorrect string literal: {0}")]
+    StrLitDecodeError(#[source] StrLitDecodeError),
+    #[error("wrong option type, expecting {0}, got `{1}`")]
+    WrongOptionType(&'static str, String),
+    #[error("Message field requires a message constant")]
+    MessageFieldRequiresMessageConstant,
+    #[error("message not found by name {0}")]
+    MessageNotFound(ProtobufAbsPath),
+    #[error("message not found by name {0}")]
+    MessageFoundMoreThanOnce(ProtobufAbsPath),
+}
+
+#[derive(Clone)]
+enum LookupScope2 {
+    File(FileDescriptor),
+    Message(MessageDescriptor, ProtobufAbsPath),
+}
+
+impl LookupScope2 {
+    fn current_path(&self) -> ProtobufAbsPath {
+        match self {
+            LookupScope2::File(f) => ProtobufAbsPath::package_from_file_descriptor(f),
+            LookupScope2::Message(_, p) => p.clone(),
+        }
+    }
+
+    fn messages(&self) -> Vec<MessageDescriptor> {
+        match self {
+            LookupScope2::File(file) => file.messages().collect(),
+            LookupScope2::Message(message, _) => message.nested_messages().collect(),
+        }
+    }
+
+    fn down(&self, name: &ProtobufIdentRef) -> Option<LookupScope2> {
+        match self.messages().iter().find(|m| m.name() == name.as_str()) {
+            Some(m) => {
+                let mut path = self.current_path();
+                path.push_simple(name.clone());
+                Some(LookupScope2::Message(m.clone(), path))
+            }
+            None => None,
+        }
+    }
+
+    fn extensions(&self) -> Vec<FieldDescriptor> {
+        match self {
+            LookupScope2::File(f) => f.extensions().collect(),
+            LookupScope2::Message(m, _) => m.extensions().collect(),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub(crate) struct LookupScopeUnion2 {
+    path: ProtobufAbsPath,
+    scopes: Vec<LookupScope2>,
+    partial_scopes: Vec<FileDescriptor>,
+}
+
+impl LookupScopeUnion2 {
+    fn down(&self, name: &ProtobufIdentRef) -> LookupScopeUnion2 {
+        let mut path: ProtobufAbsPath = self.path.clone();
+        path.push_simple(name);
+
+        let mut scopes: Vec<_> = self.scopes.iter().flat_map(|f| f.down(name)).collect();
+        let mut partial_scopes = Vec::new();
+
+        for partial_scope in &self.partial_scopes {
+            let package = ProtobufAbsPath::package_from_file_descriptor(partial_scope);
+            if package.as_ref() == path.as_ref() {
+                scopes.push(LookupScope2::File(partial_scope.clone()));
+            } else if package.starts_with(&path) {
+                partial_scopes.push(partial_scope.clone());
+            }
+        }
+        LookupScopeUnion2 {
+            path,
+            scopes,
+            partial_scopes,
+        }
+    }
+
+    fn lookup(&self, path: &ProtobufRelPath) -> LookupScopeUnion2 {
+        let mut scope = self.clone();
+        for c in path.components() {
+            scope = scope.down(c);
+        }
+        scope
+    }
+
+    fn extensions(&self) -> Vec<FieldDescriptor> {
+        self.scopes.iter().flat_map(|s| s.extensions()).collect()
+    }
+
+    fn as_message(&self) -> anyhow::Result<MessageDescriptor> {
+        let mut messages: Vec<MessageDescriptor> = self
+            .scopes
+            .iter()
+            .filter_map(|s| match s {
+                LookupScope2::Message(m, _) => Some(m.clone()),
+                _ => None,
+            })
+            .collect();
+        let message = match messages.pop() {
+            Some(m) => m,
+            None => return Err(OptionResolverError::MessageNotFound(self.path.clone()).into()),
+        };
+        if !messages.is_empty() {
+            return Err(OptionResolverError::MessageFoundMoreThanOnce(self.path.clone()).into());
+        }
+        Ok(message)
+    }
+}
+
+pub(crate) trait ProtobufOptions {
+    fn by_name(&self, name: &str) -> Option<&model::ProtobufConstant>;
+
+    fn by_name_bool(&self, name: &str) -> anyhow::Result<Option<bool>> {
+        match self.by_name(name) {
+            Some(model::ProtobufConstant::Bool(b)) => Ok(Some(*b)),
+            Some(c) => Err(OptionResolverError::WrongOptionType("bool", c.to_string()).into()),
+            None => Ok(None),
+        }
+    }
+
+    fn by_name_string(&self, name: &str) -> anyhow::Result<Option<String>> {
+        match self.by_name(name) {
+            Some(model::ProtobufConstant::String(s)) => s
+                .decode_utf8()
+                .map(Some)
+                .map_err(|e| OptionResolverError::StrLitDecodeError(e).into()),
+            Some(c) => Err(OptionResolverError::WrongOptionType("string", c.to_string()).into()),
+            None => Ok(None),
+        }
+    }
+}
+
+impl<'a> ProtobufOptions for &'a [model::ProtobufOption] {
+    fn by_name(&self, name: &str) -> Option<&model::ProtobufConstant> {
+        let option_name = ProtobufOptionName::simple(name);
+        for model::ProtobufOption { name, value } in *self {
+            if name == &option_name {
+                return Some(&value);
+            }
+        }
+        None
+    }
+}
+
+pub(crate) struct OptionResoler<'a> {
+    pub(crate) resolver: &'a Resolver<'a>,
+    pub(crate) descriptor_without_options: FileDescriptor,
+}
+
+impl<'a> OptionResoler<'a> {
+    fn all_files(&self) -> Vec<FileDescriptor> {
+        let mut files = Vec::new();
+        files.push(self.descriptor_without_options.clone());
+        files.extend(
+            self.resolver
+                .type_resolver
+                .deps
+                .iter()
+                .map(|p| p.descriptor.clone()),
+        );
+        files
+    }
+
+    fn root_scope(&self) -> LookupScopeUnion2 {
+        let (scopes, partial_scopes) = self
+            .all_files()
+            .into_iter()
+            .partition::<Vec<_>, _>(|f| ProtobufAbsPath::package_from_file_descriptor(f).is_root());
+        LookupScopeUnion2 {
+            path: ProtobufAbsPath::root(),
+            scopes: scopes.into_iter().map(LookupScope2::File).collect(),
+            partial_scopes,
+        }
+    }
+
+    fn lookup(&self, path: &ProtobufAbsPath) -> LookupScopeUnion2 {
+        self.root_scope().lookup(&path.to_root_rel())
+    }
+
+    fn find_message_by_abs_name(
+        &self,
+        path: &ProtobufAbsPath,
+    ) -> anyhow::Result<MessageDescriptor> {
+        let scope = self.lookup(path);
+        scope.as_message()
+    }
+
+    fn scope_resolved_candidates_rel(
+        scope: &ProtobufAbsPathRef,
+        rel: &ProtobufRelPathRef,
+    ) -> Vec<ProtobufAbsPath> {
+        scope
+            .self_and_parents()
+            .into_iter()
+            .map(|a| {
+                let mut a = a.to_owned();
+                a.push_relative(rel);
+                a
+            })
+            .collect()
+    }
+
+    fn scope_resolved_candidates(
+        scope: &ProtobufAbsPathRef,
+        path: &ProtobufPath,
+    ) -> Vec<ProtobufAbsPath> {
+        match path {
+            ProtobufPath::Abs(p) => vec![p.clone()],
+            ProtobufPath::Rel(p) => Self::scope_resolved_candidates_rel(scope, p),
+        }
+    }
+
+    fn find_extension_by_abs_path(
+        &self,
+        path: &ProtobufAbsPathRef,
+    ) -> anyhow::Result<Option<FieldDescriptor>> {
+        let mut path = path.to_owned();
+        let extension = path.pop().unwrap();
+
+        let scope = self.lookup(&path);
+
+        for ext in scope.extensions() {
+            if ext.name() == extension.get() {
+                return Ok(Some(ext.clone()));
+            }
+        }
+
+        Ok(None)
+    }
+
+    fn find_extension_by_path(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        path: &ProtobufPath,
+    ) -> anyhow::Result<FieldDescriptor> {
+        for candidate in Self::scope_resolved_candidates(scope, path) {
+            if let Some(e) = self.find_extension_by_abs_path(&candidate)? {
+                return Ok(e);
+            }
+        }
+
+        Err(OptionResolverError::ExtensionNotFound(path.to_string()).into())
+    }
+
+    fn ext_resolve_field_ext(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        message: &MessageDescriptor,
+        field_name: &ProtobufPath,
+    ) -> anyhow::Result<FieldDescriptor> {
+        let expected_extendee = ProtobufAbsPath::from_message(message);
+        let field = self.find_extension_by_path(scope, field_name)?;
+        if ProtobufAbsPath::new(field.proto().extendee()) != expected_extendee {
+            return Err(OptionResolverError::WrongExtensionType(
+                format!("{}", field_name),
+                format!("{}", field.proto().extendee()),
+                format!("{}", expected_extendee),
+            )
+            .into());
+        }
+
+        Ok(field)
+    }
+
+    fn ext_resolve_field(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        message: &MessageDescriptor,
+        field: &ProtobufOptionNamePart,
+    ) -> anyhow::Result<FieldDescriptor> {
+        match field {
+            ProtobufOptionNamePart::Direct(field) => match message.field_by_name(field.get()) {
+                Some(field) => Ok(field),
+                None => Err(OptionResolverError::UnknownFieldName(field.to_string()).into()),
+            },
+            ProtobufOptionNamePart::Ext(field) => {
+                Ok(self.ext_resolve_field_ext(scope, message, field)?)
+            }
+        }
+    }
+
+    fn custom_option_ext_step(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        options_type: &MessageDescriptor,
+        options: &mut UnknownFields,
+        option_name: &ProtobufOptionNamePart,
+        option_name_rem: &[ProtobufOptionNamePart],
+        option_value: &ProtobufConstant,
+    ) -> anyhow::Result<()> {
+        let field = self.ext_resolve_field(scope, options_type, option_name)?;
+
+        let field_type = TypeResolved::from_field(field.proto());
+
+        match option_name_rem.split_first() {
+            Some((first, rem)) => {
+                match field_type {
+                    TypeResolved::Message(message_name) => {
+                        let m = self.find_message_by_abs_name(&message_name)?;
+                        let mut unknown_fields = UnknownFields::new();
+                        self.custom_option_ext_step(
+                            scope,
+                            &m,
+                            &mut unknown_fields,
+                            first,
+                            rem,
+                            option_value,
+                        )?;
+                        options.add_length_delimited(
+                            field.proto().number() as u32,
+                            unknown_fields.write_to_bytes(),
+                        );
+                        Ok(())
+                    }
+                    TypeResolved::Group(..) => {
+                        // TODO: implement
+                        Ok(())
+                    }
+                    _ => Err(OptionResolverError::ExtensionIsNotMessage(format!(
+                        "scope: {}, option name: {}",
+                        scope, option_name
+                    ))
+                    .into()),
+                }
+            }
+            None => {
+                let value = match self.option_value_to_unknown_value(
+                    &field_type,
+                    option_value,
+                    &format!("{}", option_name),
+                ) {
+                    Ok(value) => value,
+                    Err(e) => {
+                        let e = e.context(format!(
+                            "parsing custom option `{}` value `{}` at `{}`",
+                            option_name, option_value, scope
+                        ));
+                        return Err(e.into());
+                    }
+                };
+
+                options.add_value(field.proto().number() as u32, value);
+                Ok(())
+            }
+        }
+    }
+
+    fn custom_option_ext<M>(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        options: &mut M,
+        option_name: &ProtobufOptionNameExt,
+        option_value: &ProtobufConstant,
+    ) -> anyhow::Result<()>
+    where
+        M: MessageFull,
+    {
+        self.custom_option_ext_step(
+            scope,
+            &M::descriptor(),
+            options.mut_unknown_fields(),
+            &option_name.0[0],
+            &option_name.0[1..],
+            option_value,
+        )
+    }
+
+    fn fixed32(
+        v: impl TryInto<u32, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::Fixed32(v.try_into()?))
+    }
+
+    fn sfixed32(
+        v: impl TryInto<i32, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::sfixed32(v.try_into()?))
+    }
+
+    fn fixed64(
+        v: impl TryInto<u64, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::Fixed64(v.try_into()?))
+    }
+
+    fn sfixed64(
+        v: impl TryInto<i64, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::sfixed64(v.try_into()?))
+    }
+
+    fn int32(
+        v: impl TryInto<i32, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::int32(v.try_into()?))
+    }
+
+    fn int64(
+        v: impl TryInto<i64, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::int64(v.try_into()?))
+    }
+
+    fn uint32(
+        v: impl TryInto<u32, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::Varint(v.try_into()? as u64))
+    }
+
+    fn uint64(
+        v: impl TryInto<u64, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::Varint(v.try_into()?))
+    }
+
+    fn sint32(
+        v: impl TryInto<i32, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::sint32(v.try_into()?))
+    }
+
+    fn sint64(
+        v: impl TryInto<i64, Error = impl std::error::Error + Send + Sync + 'static>,
+    ) -> anyhow::Result<UnknownValue> {
+        Ok(UnknownValue::sint64(v.try_into()?))
+    }
+
+    fn option_value_message_to_unknown_value(
+        &self,
+        field_type: &TypeResolved,
+        value: &ProtobufConstantMessage,
+        option_name_for_diag: &str,
+    ) -> anyhow::Result<UnknownValue> {
+        match &field_type {
+            TypeResolved::Message(ma) => {
+                let m = self
+                    .resolver
+                    .find_message_by_abs_name(ma)
+                    .map_err(OptionResolverError::OtherError)?
+                    .t;
+                let mut unknown_fields = UnknownFields::new();
+                for (n, v) in &value.fields {
+                    match n {
+                        ProtobufConstantMessageFieldName::Regular(n) => {
+                            let f = match m.field_by_name(n.as_str()) {
+                                Some(f) => f,
+                                None => {
+                                    return Err(
+                                        OptionResolverError::UnknownFieldName(n.clone()).into()
+                                    )
+                                }
+                            };
+                            let u = self
+                                .option_value_field_to_unknown_value(
+                                    ma,
+                                    v,
+                                    n,
+                                    &f.typ,
+                                    option_name_for_diag,
+                                )
+                                .map_err(OptionResolverError::OtherError)?;
+                            unknown_fields.add_value(f.number as u32, u);
+                        }
+                        ProtobufConstantMessageFieldName::Extension(..) => {
+                            // TODO: implement extension fields in constants
+                        }
+                        ProtobufConstantMessageFieldName::AnyTypeUrl(..) => {
+                            // TODO: implement any type url in constants
+                        }
+                    }
+                }
+                Ok(UnknownValue::LengthDelimited(
+                    unknown_fields.write_to_bytes(),
+                ))
+            }
+            _ => Err(OptionResolverError::MessageFieldRequiresMessageConstant.into()),
+        }
+    }
+
+    fn option_value_to_unknown_value(
+        &self,
+        field_type: &TypeResolved,
+        value: &model::ProtobufConstant,
+        option_name_for_diag: &str,
+    ) -> anyhow::Result<UnknownValue> {
+        match value {
+            &model::ProtobufConstant::Bool(b) => {
+                if field_type != &TypeResolved::Bool {
+                    {}
+                } else {
+                    return Ok(UnknownValue::Varint(if b { 1 } else { 0 }));
+                }
+            }
+            &model::ProtobufConstant::U64(v) => match field_type {
+                TypeResolved::Fixed64 => return Self::fixed64(v),
+                TypeResolved::Sfixed64 => return Self::sfixed64(v),
+                TypeResolved::Fixed32 => return Self::fixed32(v),
+                TypeResolved::Sfixed32 => return Self::sfixed32(v),
+                TypeResolved::Int32 => return Self::int32(v),
+                TypeResolved::Int64 => return Self::int64(v),
+                TypeResolved::Uint64 => return Self::uint64(v),
+                TypeResolved::Uint32 => return Self::uint32(v),
+                TypeResolved::Sint64 => return Self::sint64(v),
+                TypeResolved::Sint32 => return Self::sint32(v),
+                TypeResolved::Float => return Ok(UnknownValue::float(v as f32)),
+                TypeResolved::Double => return Ok(UnknownValue::double(v as f64)),
+                _ => {}
+            },
+            &model::ProtobufConstant::I64(v) => match field_type {
+                TypeResolved::Fixed64 => return Self::fixed64(v),
+                TypeResolved::Sfixed64 => return Self::sfixed64(v),
+                TypeResolved::Fixed32 => return Self::fixed32(v),
+                TypeResolved::Sfixed32 => return Self::sfixed32(v),
+                TypeResolved::Int64 => return Self::int64(v),
+                TypeResolved::Int32 => return Self::int32(v),
+                TypeResolved::Uint64 => return Self::uint64(v),
+                TypeResolved::Uint32 => return Self::uint32(v),
+                TypeResolved::Sint64 => return Self::sint64(v),
+                TypeResolved::Sint32 => return Self::sint32(v),
+                TypeResolved::Float => return Ok(UnknownValue::float(v as f32)),
+                TypeResolved::Double => return Ok(UnknownValue::double(v as f64)),
+                _ => {}
+            },
+            &model::ProtobufConstant::F64(f) => match field_type {
+                TypeResolved::Float => return Ok(UnknownValue::float(f as f32)),
+                TypeResolved::Double => return Ok(UnknownValue::double(f)),
+                TypeResolved::Fixed32 => return Ok(UnknownValue::Fixed32(f as u32)),
+                TypeResolved::Fixed64 => return Ok(UnknownValue::Fixed64(f as u64)),
+                TypeResolved::Sfixed32 => return Ok(UnknownValue::sfixed32(f as i32)),
+                TypeResolved::Sfixed64 => return Ok(UnknownValue::sfixed64(f as i64)),
+                TypeResolved::Int32 | TypeResolved::Int64 => {
+                    return Ok(UnknownValue::int64(f as i64))
+                }
+                TypeResolved::Uint32 | TypeResolved::Uint64 => {
+                    return Ok(UnknownValue::Varint(f as u64))
+                }
+                TypeResolved::Sint64 => return Ok(UnknownValue::sint64(f as i64)),
+                TypeResolved::Sint32 => return Ok(UnknownValue::sint32(f as i32)),
+                _ => {}
+            },
+            &model::ProtobufConstant::String(ref s) => match field_type {
+                TypeResolved::String => {
+                    return Ok(UnknownValue::LengthDelimited(s.decode_utf8()?.into_bytes()))
+                }
+                TypeResolved::Bytes => return Ok(UnknownValue::LengthDelimited(s.decode_bytes()?)),
+                _ => {}
+            },
+            model::ProtobufConstant::Ident(ident) => match &field_type {
+                TypeResolved::Enum(e) => {
+                    let e = self
+                        .resolver
+                        .find_enum_by_abs_name(e)
+                        .map_err(OptionResolverError::OtherError)?;
+                    let n = match e
+                        .values
+                        .iter()
+                        .find(|v| v.name == format!("{}", ident))
+                        .map(|v| v.number)
+                    {
+                        Some(n) => n,
+                        None => {
+                            return Err(
+                                OptionResolverError::UnknownEnumValue(ident.to_string()).into()
+                            )
+                        }
+                    };
+                    return Ok(UnknownValue::int32(n));
+                }
+                _ => {}
+            },
+            model::ProtobufConstant::Message(mo) => {
+                return self.option_value_message_to_unknown_value(
+                    &field_type,
+                    mo,
+                    option_name_for_diag,
+                );
+            }
+        };
+
+        Err(match field_type {
+            _ => OptionResolverError::UnsupportedExtensionType(
+                option_name_for_diag.to_owned(),
+                format!("{:?}", field_type),
+                value.clone(),
+            )
+            .into(),
+        })
+    }
+
+    fn option_value_field_to_unknown_value(
+        &self,
+        scope: &ProtobufAbsPath,
+        value: &model::ProtobufConstant,
+        name: &str,
+        field_type: &model::FieldType,
+        option_name_for_diag: &str,
+    ) -> anyhow::Result<UnknownValue> {
+        let field_type = self.resolver.field_type(&scope, name, field_type)?;
+        Ok(self
+            .option_value_to_unknown_value(&field_type, value, option_name_for_diag)
+            .context("parsing custom option value")?)
+    }
+
+    fn custom_option_builtin<M>(
+        &self,
+        _scope: &ProtobufAbsPathRef,
+        options: &mut M,
+        option: &ProtobufIdent,
+        option_value: &ProtobufConstant,
+    ) -> anyhow::Result<()>
+    where
+        M: MessageFull,
+    {
+        if M::descriptor().full_name() == "google.protobuf.FieldOptions" {
+            if option.get() == "default" || option.get() == "json_name" {
+                // some options are written to non-options message and handled outside
+                return Ok(());
+            }
+        }
+        match M::descriptor().field_by_name(option.get()) {
+            Some(field) => {
+                if field.is_repeated_or_map() {
+                    return Err(OptionResolverError::BuiltinOptionPointsToNonSingularField(
+                        M::descriptor().full_name().to_owned(),
+                        option.get().to_owned(),
+                    )
+                    .into());
+                }
+
+                field.set_singular_field(
+                    options,
+                    option_value.as_type(field.singular_runtime_type())?,
+                );
+                return Ok(());
+            }
+            None => {
+                return Err(OptionResolverError::BuiltinOptionNotFound(
+                    M::descriptor().full_name().to_owned(),
+                    option.get().to_owned(),
+                )
+                .into())
+            }
+        }
+    }
+
+    fn custom_option<M>(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        options: &mut M,
+        option: &model::ProtobufOption,
+    ) -> anyhow::Result<()>
+    where
+        M: MessageFull,
+    {
+        match &option.name {
+            ProtobufOptionName::Builtin(simple) => {
+                self.custom_option_builtin(scope, options, simple, &option.value)
+            }
+            ProtobufOptionName::Ext(e) => self.custom_option_ext(scope, options, e, &option.value),
+        }
+    }
+
+    fn custom_options<M>(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<M>>
+    where
+        M: MessageFull,
+    {
+        if input.is_empty() {
+            // Empty options do not have to represented to unset message field,
+            // but this is what Google's parser does.
+            return Ok(None);
+        }
+
+        let mut options = M::new();
+
+        for option in input {
+            self.custom_option(scope, &mut options, option)?;
+        }
+        Ok(Some(options))
+    }
+
+    fn file_options(
+        &self,
+        scope: &ProtobufAbsPath,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::FileOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn enum_options(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::EnumOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn enum_value_options(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::EnumValueOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn field_options(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::FieldOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn message_options(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::MessageOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn oneof_options(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::OneofOptions>> {
+        self.custom_options(scope, input)
+    }
+
+    fn method(
+        &self,
+        method_proto: &mut MethodDescriptorProto,
+        method_model: &model::Method,
+    ) -> anyhow::Result<()> {
+        method_proto.options = self.service_method_options(&method_model.options)?.into();
+        Ok(())
+    }
+
+    fn service_options(
+        &self,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::ServiceOptions>> {
+        self.custom_options(&self.resolver.current_file.package, input)
+    }
+
+    fn service_method_options(
+        &self,
+        input: &[model::ProtobufOption],
+    ) -> anyhow::Result<Option<protobuf::descriptor::MethodOptions>> {
+        self.custom_options(&self.resolver.current_file.package, input)
+    }
+
+    fn service(
+        &self,
+        service_proto: &mut ServiceDescriptorProto,
+        service_model: &WithLoc<model::Service>,
+    ) -> anyhow::Result<()> {
+        service_proto.options = self.service_options(&service_model.options)?.into();
+
+        for service_method_model in &service_model.methods {
+            let mut method_proto = service_proto
+                .method
+                .iter_mut()
+                .find(|method| method.name() == service_method_model.name)
+                .unwrap();
+            self.method(&mut method_proto, service_method_model)?;
+        }
+
+        Ok(())
+    }
+
+    fn enum_value(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        enum_value_proto: &mut EnumValueDescriptorProto,
+        enum_value_model: &model::EnumValue,
+    ) -> anyhow::Result<()> {
+        enum_value_proto.options = self
+            .enum_value_options(scope, &enum_value_model.options)?
+            .into();
+        Ok(())
+    }
+
+    fn enumeration(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        enum_proto: &mut EnumDescriptorProto,
+        enum_model: &WithLoc<model::Enumeration>,
+    ) -> anyhow::Result<()> {
+        enum_proto.options = self.enum_options(scope, &enum_model.options)?.into();
+
+        for enum_value_model in &enum_model.values {
+            let mut enum_value_proto = enum_proto
+                .value
+                .iter_mut()
+                .find(|v| v.name() == enum_value_model.name)
+                .unwrap();
+            self.enum_value(scope, &mut enum_value_proto, enum_value_model)?;
+        }
+
+        Ok(())
+    }
+
+    fn oneof(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        oneof_proto: &mut OneofDescriptorProto,
+        oneof_model: &model::OneOf,
+    ) -> anyhow::Result<()> {
+        oneof_proto.options = self.oneof_options(scope, &oneof_model.options)?.into();
+        Ok(())
+    }
+
+    fn field(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        field_proto: &mut FieldDescriptorProto,
+        field_model: &model::Field,
+    ) -> anyhow::Result<()> {
+        field_proto.options = self.field_options(scope, &field_model.options)?.into();
+        Ok(())
+    }
+
+    fn message(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        message_proto: &mut DescriptorProto,
+        message_model: &WithLoc<model::Message>,
+    ) -> anyhow::Result<()> {
+        message_proto.options = self.message_options(scope, &message_model.options)?.into();
+
+        let mut nested_scope = scope.to_owned();
+        nested_scope.push_simple(ProtobufIdentRef::new(&message_proto.name()));
+
+        for field_model in &message_model.regular_fields_including_in_oneofs() {
+            let mut field_proto = message_proto
+                .field
+                .iter_mut()
+                .find(|field| field.name() == field_model.name)
+                .unwrap();
+            self.field(&nested_scope, &mut field_proto, field_model)?;
+        }
+        for field_model in &message_model.extensions {
+            let field_proto = message_proto
+                .extension
+                .iter_mut()
+                .find(|field| field.name() == field_model.field.name)
+                .unwrap();
+            self.field(&nested_scope, field_proto, &field_model.field)?;
+        }
+
+        for nested_message_model in &message_model.messages {
+            let nested_message_proto = message_proto
+                .nested_type
+                .iter_mut()
+                .find(|nested_message_proto| {
+                    nested_message_proto.name() == nested_message_model.name
+                })
+                .unwrap();
+            self.message(&nested_scope, nested_message_proto, nested_message_model)?;
+        }
+
+        for nested_enum_model in &message_model.enums {
+            let nested_enum_proto = message_proto
+                .enum_type
+                .iter_mut()
+                .find(|nested_enum_proto| nested_enum_proto.name() == nested_enum_model.name)
+                .unwrap();
+            self.enumeration(&nested_scope, nested_enum_proto, nested_enum_model)?;
+        }
+
+        for oneof_model in &message_model.oneofs() {
+            let oneof_proto = message_proto
+                .oneof_decl
+                .iter_mut()
+                .find(|oneof_proto| oneof_proto.name() == oneof_model.name)
+                .unwrap();
+            self.oneof(&nested_scope, oneof_proto, oneof_model)?;
+        }
+
+        Ok(())
+    }
+
+    pub(crate) fn file(&self, output: &mut FileDescriptorProto) -> anyhow::Result<()> {
+        // TODO: use it to resolve messages.
+        let _ = &self.descriptor_without_options;
+
+        for message_model in &self.resolver.current_file.messages {
+            let message_proto = output
+                .message_type
+                .iter_mut()
+                .find(|m| m.name() == message_model.name)
+                .unwrap();
+            self.message(
+                &self.resolver.current_file.package,
+                message_proto,
+                message_model,
+            )?;
+        }
+
+        for enum_model in &self.resolver.current_file.enums {
+            let enum_proto = output
+                .enum_type
+                .iter_mut()
+                .find(|e| e.name() == enum_model.name)
+                .unwrap();
+            self.enumeration(&self.resolver.current_file.package, enum_proto, enum_model)?;
+        }
+
+        for service_proto in &mut output.service {
+            let service_model = self
+                .resolver
+                .current_file
+                .services
+                .iter()
+                .find(|s| s.name == service_proto.name())
+                .unwrap();
+            self.service(service_proto, service_model)?;
+        }
+
+        for extension_model in &self.resolver.current_file.extensions {
+            let extension_proto = output
+                .extension
+                .iter_mut()
+                .find(|e| e.name() == extension_model.field.name)
+                .unwrap();
+            self.field(
+                &self.resolver.current_file.package,
+                extension_proto,
+                &extension_model.field,
+            )?;
+        }
+
+        output.options = self
+            .file_options(
+                &self.resolver.current_file.package,
+                &self.resolver.current_file.options,
+            )?
+            .into();
+
+        Ok(())
+    }
+}
diff --git a/src/pure/convert/type_resolver.rs b/src/pure/convert/type_resolver.rs
new file mode 100644
index 0000000..f054923
--- /dev/null
+++ b/src/pure/convert/type_resolver.rs
@@ -0,0 +1,189 @@
+use std::iter;
+
+use crate::model;
+use crate::model::WithLoc;
+use crate::protobuf_path::ProtobufPath;
+use crate::pure::convert::WithFullName;
+use crate::FileDescriptorPair;
+use crate::ProtobufAbsPath;
+use crate::ProtobufAbsPathRef;
+use crate::ProtobufIdent;
+use crate::ProtobufIdentRef;
+use crate::ProtobufRelPath;
+use crate::ProtobufRelPathRef;
+
+#[derive(thiserror::Error, Debug)]
+enum TypeResolverError {
+    #[error("object is not found by path: {0}")]
+    NotFoundByAbsPath(ProtobufAbsPath),
+    #[error("object is not found by path `{0}` in scope `{1}`")]
+    NotFoundByRelPath(ProtobufRelPath, ProtobufAbsPath),
+}
+
+pub(crate) enum MessageOrEnum<'a> {
+    Message(&'a model::Message),
+    Enum(&'a model::Enumeration),
+}
+
+impl MessageOrEnum<'_> {
+    fn _descriptor_type(&self) -> protobuf::descriptor::field_descriptor_proto::Type {
+        match *self {
+            MessageOrEnum::Message(..) => {
+                protobuf::descriptor::field_descriptor_proto::Type::TYPE_MESSAGE
+            }
+            MessageOrEnum::Enum(..) => {
+                protobuf::descriptor::field_descriptor_proto::Type::TYPE_ENUM
+            }
+        }
+    }
+}
+
+#[derive(Clone)]
+enum LookupScope<'a> {
+    File(&'a model::FileDescriptor),
+    Message(&'a model::Message, ProtobufAbsPath),
+}
+
+impl<'a> LookupScope<'a> {
+    fn current_path(&self) -> ProtobufAbsPath {
+        match self {
+            LookupScope::File(f) => f.package.clone(),
+            LookupScope::Message(_, p) => p.clone(),
+        }
+    }
+
+    fn messages(&self) -> &'a [model::WithLoc<model::Message>] {
+        match self {
+            &LookupScope::File(file) => &file.messages,
+            &LookupScope::Message(messasge, _) => &messasge.messages,
+        }
+    }
+
+    fn find_message(&self, simple_name: &ProtobufIdentRef) -> Option<&'a model::Message> {
+        self.messages()
+            .into_iter()
+            .find(|m| m.t.name == simple_name.as_str())
+            .map(|m| &m.t)
+    }
+
+    fn enums(&self) -> &'a [WithLoc<model::Enumeration>] {
+        match self {
+            &LookupScope::File(file) => &file.enums,
+            &LookupScope::Message(messasge, _) => &messasge.enums,
+        }
+    }
+
+    fn members(&self) -> Vec<(ProtobufIdent, MessageOrEnum<'a>)> {
+        let mut r = Vec::new();
+        r.extend(
+            self.enums()
+                .into_iter()
+                .map(|e| (ProtobufIdent::from(&e.name[..]), MessageOrEnum::Enum(e))),
+        );
+        r.extend(self.messages().into_iter().map(|m| {
+            (
+                ProtobufIdent::from(&m.t.name[..]),
+                MessageOrEnum::Message(&m.t),
+            )
+        }));
+        r
+    }
+
+    fn find_member(&self, simple_name: &ProtobufIdentRef) -> Option<MessageOrEnum<'a>> {
+        self.members()
+            .into_iter()
+            .filter_map(|(member_name, message_or_enum)| {
+                if member_name.as_ref() == simple_name {
+                    Some(message_or_enum)
+                } else {
+                    None
+                }
+            })
+            .next()
+    }
+
+    pub(crate) fn find_message_or_enum(
+        &self,
+        path: &ProtobufRelPathRef,
+    ) -> Option<WithFullName<MessageOrEnum<'a>>> {
+        let current_path = self.current_path();
+        let (first, rem) = match path.split_first_rem() {
+            Some(x) => x,
+            None => return None,
+        };
+
+        if rem.is_empty() {
+            match self.find_member(first) {
+                Some(message_or_enum) => {
+                    let mut result_path = current_path.clone();
+                    result_path.push_simple(first);
+                    Some(WithFullName {
+                        full_name: result_path,
+                        t: message_or_enum,
+                    })
+                }
+                None => None,
+            }
+        } else {
+            match self.find_message(first) {
+                Some(message) => {
+                    let mut message_path = current_path.clone();
+                    message_path.push_simple(ProtobufIdentRef::new(&message.name));
+                    let message_scope = LookupScope::Message(message, message_path);
+                    message_scope.find_message_or_enum(rem)
+                }
+                None => None,
+            }
+        }
+    }
+}
+
+pub(crate) struct TypeResolver<'a> {
+    pub(crate) current_file: &'a model::FileDescriptor,
+    pub(crate) deps: &'a [FileDescriptorPair],
+}
+
+impl<'a> TypeResolver<'a> {
+    pub(crate) fn all_files(&self) -> Vec<&'a model::FileDescriptor> {
+        iter::once(self.current_file)
+            .chain(self.deps.iter().map(|p| &p.parsed))
+            .collect()
+    }
+
+    pub(crate) fn find_message_or_enum_by_abs_name(
+        &self,
+        absolute_path: &ProtobufAbsPath,
+    ) -> anyhow::Result<WithFullName<MessageOrEnum<'a>>> {
+        for file in self.all_files() {
+            if let Some(relative) = absolute_path.remove_prefix(&file.package) {
+                if let Some(w) = LookupScope::File(file).find_message_or_enum(&relative) {
+                    return Ok(w);
+                }
+            }
+        }
+
+        return Err(TypeResolverError::NotFoundByAbsPath(absolute_path.clone()).into());
+    }
+
+    pub(crate) fn resolve_message_or_enum(
+        &self,
+        scope: &ProtobufAbsPathRef,
+        name: &ProtobufPath,
+    ) -> anyhow::Result<WithFullName<MessageOrEnum>> {
+        match name {
+            ProtobufPath::Abs(name) => Ok(self.find_message_or_enum_by_abs_name(&name)?),
+            ProtobufPath::Rel(name) => {
+                // find message or enum in current package
+                for p in scope.self_and_parents() {
+                    let mut fq = p.to_owned();
+                    fq.push_relative(&name);
+                    if let Ok(me) = self.find_message_or_enum_by_abs_name(&fq) {
+                        return Ok(me);
+                    }
+                }
+
+                Err(TypeResolverError::NotFoundByRelPath(name.clone(), scope.to_owned()).into())
+            }
+        }
+    }
+}
diff --git a/src/pure/mod.rs b/src/pure/mod.rs
new file mode 100644
index 0000000..c60fb2f
--- /dev/null
+++ b/src/pure/mod.rs
@@ -0,0 +1,10 @@
+//! Pure rust `.proto` file parser.
+
+pub(crate) mod convert;
+pub(crate) mod model;
+pub(crate) mod parse_and_typecheck;
+pub(crate) mod parse_dependencies;
+mod parser;
+
+pub use parse_and_typecheck::parse_and_typecheck_custom;
+pub use parse_dependencies::*;
diff --git a/src/pure/model.rs b/src/pure/model.rs
new file mode 100644
index 0000000..3efdc11
--- /dev/null
+++ b/src/pure/model.rs
@@ -0,0 +1,590 @@
+//! A nom-based protobuf file parser
+//!
+//! This crate can be seen as a rust transcription of the
+//! [descriptor.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto) file
+
+use std::fmt;
+use std::fmt::Write;
+use std::ops::Deref;
+
+use indexmap::IndexMap;
+use protobuf::reflect::ReflectValueBox;
+use protobuf::reflect::RuntimeType;
+use protobuf_support::lexer::float::format_protobuf_float;
+use protobuf_support::lexer::loc::Loc;
+use protobuf_support::lexer::str_lit::StrLit;
+
+use crate::model;
+use crate::proto_path::ProtoPathBuf;
+use crate::protobuf_abs_path::ProtobufAbsPath;
+use crate::protobuf_ident::ProtobufIdent;
+use crate::protobuf_path::ProtobufPath;
+use crate::pure::parser::Parser;
+pub use crate::pure::parser::ParserErrorWithLocation;
+
+#[derive(thiserror::Error, Debug)]
+enum ModelError {
+    #[error("cannot convert value `{1}` to type `{0}`")]
+    InconvertibleValue(RuntimeType, model::ProtobufConstant),
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct WithLoc<T> {
+    pub loc: Loc,
+    pub t: T,
+}
+
+impl<T> Deref for WithLoc<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.t
+    }
+}
+
+impl<T> WithLoc<T> {
+    pub fn with_loc(loc: Loc) -> impl FnOnce(T) -> WithLoc<T> {
+        move |t| WithLoc {
+            t,
+            loc: loc.clone(),
+        }
+    }
+}
+
+/// Protobuf syntax.
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub(crate) enum Syntax {
+    /// Protobuf syntax [2](https://developers.google.com/protocol-buffers/docs/proto) (default)
+    Proto2,
+    /// Protobuf syntax [3](https://developers.google.com/protocol-buffers/docs/proto3)
+    Proto3,
+}
+
+impl Default for Syntax {
+    fn default() -> Syntax {
+        Syntax::Proto2
+    }
+}
+
+/// A field rule
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub(crate) enum Rule {
+    /// A well-formed message can have zero or one of this field (but not more than one).
+    Optional,
+    /// This field can be repeated any number of times (including zero) in a well-formed message.
+    /// The order of the repeated values will be preserved.
+    Repeated,
+    /// A well-formed message must have exactly one of this field.
+    Required,
+}
+
+impl Rule {
+    pub(crate) const ALL: [Rule; 3] = [Rule::Optional, Rule::Repeated, Rule::Required];
+
+    pub(crate) const fn as_str(&self) -> &'static str {
+        match self {
+            Rule::Optional => "optional",
+            Rule::Repeated => "repeated",
+            Rule::Required => "required",
+        }
+    }
+}
+
+/// Protobuf group
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct Group {
+    /// Group name
+    pub name: String,
+    pub fields: Vec<WithLoc<Field>>,
+}
+
+/// Protobuf supported field types
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) enum FieldType {
+    /// Protobuf int32
+    ///
+    /// # Remarks
+    ///
+    /// Uses variable-length encoding. Inefficient for encoding negative numbers – if
+    /// your field is likely to have negative values, use sint32 instead.
+    Int32,
+    /// Protobuf int64
+    ///
+    /// # Remarks
+    ///
+    /// Uses variable-length encoding. Inefficient for encoding negative numbers – if
+    /// your field is likely to have negative values, use sint64 instead.
+    Int64,
+    /// Protobuf uint32
+    ///
+    /// # Remarks
+    ///
+    /// Uses variable-length encoding.
+    Uint32,
+    /// Protobuf uint64
+    ///
+    /// # Remarks
+    ///
+    /// Uses variable-length encoding.
+    Uint64,
+    /// Protobuf sint32
+    ///
+    /// # Remarks
+    ///
+    /// Uses ZigZag variable-length encoding. Signed int value. These more efficiently
+    /// encode negative numbers than regular int32s.
+    Sint32,
+    /// Protobuf sint64
+    ///
+    /// # Remarks
+    ///
+    /// Uses ZigZag variable-length encoding. Signed int value. These more efficiently
+    /// encode negative numbers than regular int32s.
+    Sint64,
+    /// Protobuf bool
+    Bool,
+    /// Protobuf fixed64
+    ///
+    /// # Remarks
+    ///
+    /// Always eight bytes. More efficient than uint64 if values are often greater than 2^56.
+    Fixed64,
+    /// Protobuf sfixed64
+    ///
+    /// # Remarks
+    ///
+    /// Always eight bytes.
+    Sfixed64,
+    /// Protobuf double
+    Double,
+    /// Protobuf string
+    ///
+    /// # Remarks
+    ///
+    /// A string must always contain UTF-8 encoded or 7-bit ASCII text.
+    String,
+    /// Protobuf bytes
+    ///
+    /// # Remarks
+    ///
+    /// May contain any arbitrary sequence of bytes.
+    Bytes,
+    /// Protobut fixed32
+    ///
+    /// # Remarks
+    ///
+    /// Always four bytes. More efficient than uint32 if values are often greater than 2^28.
+    Fixed32,
+    /// Protobut sfixed32
+    ///
+    /// # Remarks
+    ///
+    /// Always four bytes.
+    Sfixed32,
+    /// Protobut float
+    Float,
+    /// Protobuf message or enum (holds the name)
+    MessageOrEnum(ProtobufPath),
+    /// Protobut map
+    Map(Box<(FieldType, FieldType)>),
+    /// Protobuf group (deprecated)
+    Group(Group),
+}
+
+/// A Protobuf Field
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct Field {
+    /// Field name
+    pub name: String,
+    /// Field `Rule`
+    pub rule: Option<Rule>,
+    /// Field type
+    pub typ: FieldType,
+    /// Tag number
+    pub number: i32,
+    /// Non-builtin options
+    pub options: Vec<ProtobufOption>,
+}
+
+/// A Protobuf field of oneof group
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) enum FieldOrOneOf {
+    Field(WithLoc<Field>),
+    OneOf(OneOf),
+}
+
+/// Extension range
+#[derive(Default, Debug, Eq, PartialEq, Copy, Clone)]
+pub(crate) struct FieldNumberRange {
+    /// First number
+    pub from: i32,
+    /// Inclusive
+    pub to: i32,
+}
+
+/// A protobuf message
+#[derive(Debug, Clone, Default)]
+pub(crate) struct Message {
+    /// Message name
+    pub name: String,
+    /// Message fields and oneofs
+    pub fields: Vec<WithLoc<FieldOrOneOf>>,
+    /// Message reserved numbers
+    ///
+    /// TODO: use RangeInclusive once stable
+    pub reserved_nums: Vec<FieldNumberRange>,
+    /// Message reserved names
+    pub reserved_names: Vec<String>,
+    /// Nested messages
+    pub messages: Vec<WithLoc<Message>>,
+    /// Nested enums
+    pub enums: Vec<WithLoc<Enumeration>>,
+    /// Non-builtin options
+    pub options: Vec<ProtobufOption>,
+    /// Extension field numbers
+    pub extension_ranges: Vec<FieldNumberRange>,
+    /// Extensions
+    pub extensions: Vec<WithLoc<Extension>>,
+}
+
+impl Message {
+    pub fn regular_fields_including_in_oneofs(&self) -> Vec<&WithLoc<Field>> {
+        self.fields
+            .iter()
+            .flat_map(|fo| match &fo.t {
+                FieldOrOneOf::Field(f) => vec![f],
+                FieldOrOneOf::OneOf(o) => o.fields.iter().collect(),
+            })
+            .collect()
+    }
+
+    /** Find a field by name. */
+    pub fn field_by_name(&self, name: &str) -> Option<&Field> {
+        self.regular_fields_including_in_oneofs()
+            .iter()
+            .find(|f| f.t.name == name)
+            .map(|f| &f.t)
+    }
+
+    pub fn _nested_extensions(&self) -> Vec<&Group> {
+        self.regular_fields_including_in_oneofs()
+            .into_iter()
+            .flat_map(|f| match &f.t.typ {
+                FieldType::Group(g) => Some(g),
+                _ => None,
+            })
+            .collect()
+    }
+
+    #[cfg(test)]
+    pub fn regular_fields_for_test(&self) -> Vec<&Field> {
+        self.fields
+            .iter()
+            .flat_map(|fo| match &fo.t {
+                FieldOrOneOf::Field(f) => Some(&f.t),
+                FieldOrOneOf::OneOf(_) => None,
+            })
+            .collect()
+    }
+
+    pub(crate) fn oneofs(&self) -> Vec<&OneOf> {
+        self.fields
+            .iter()
+            .flat_map(|fo| match &fo.t {
+                FieldOrOneOf::Field(_) => None,
+                FieldOrOneOf::OneOf(o) => Some(o),
+            })
+            .collect()
+    }
+}
+
+/// A protobuf enumeration field
+#[derive(Debug, Clone)]
+pub(crate) struct EnumValue {
+    /// enum value name
+    pub name: String,
+    /// enum value number
+    pub number: i32,
+    /// enum value options
+    pub options: Vec<ProtobufOption>,
+}
+
+/// A protobuf enumerator
+#[derive(Debug, Clone)]
+pub(crate) struct Enumeration {
+    /// enum name
+    pub name: String,
+    /// enum values
+    pub values: Vec<EnumValue>,
+    /// enum options
+    pub options: Vec<ProtobufOption>,
+}
+
+/// A OneOf
+#[derive(Debug, Clone, Default, PartialEq)]
+pub(crate) struct OneOf {
+    /// OneOf name
+    pub name: String,
+    /// OneOf fields
+    pub fields: Vec<WithLoc<Field>>,
+    /// oneof options
+    pub options: Vec<ProtobufOption>,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct Extension {
+    /// Extend this type with field
+    pub extendee: ProtobufPath,
+    /// Extension field
+    pub field: WithLoc<Field>,
+}
+
+/// Service method
+#[derive(Debug, Clone)]
+pub(crate) struct Method {
+    /// Method name
+    pub name: String,
+    /// Input type
+    pub input_type: ProtobufPath,
+    /// Output type
+    pub output_type: ProtobufPath,
+    /// If this method is client streaming
+    #[allow(dead_code)] // TODO
+    pub client_streaming: bool,
+    /// If this method is server streaming
+    #[allow(dead_code)] // TODO
+    pub server_streaming: bool,
+    /// Method options
+    pub options: Vec<ProtobufOption>,
+}
+
+/// Service definition
+#[derive(Debug, Clone)]
+pub(crate) struct Service {
+    /// Service name
+    pub name: String,
+    pub methods: Vec<Method>,
+    pub options: Vec<ProtobufOption>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub(crate) struct AnyTypeUrl {
+    pub(crate) prefix: String,
+    pub(crate) full_type_name: ProtobufPath,
+}
+
+impl fmt::Display for AnyTypeUrl {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}/{}", self.prefix, self.full_type_name)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub(crate) enum ProtobufConstantMessageFieldName {
+    Regular(String),
+    Extension(ProtobufPath),
+    AnyTypeUrl(AnyTypeUrl),
+}
+
+impl fmt::Display for ProtobufConstantMessageFieldName {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProtobufConstantMessageFieldName::Regular(s) => write!(f, "{}", s),
+            ProtobufConstantMessageFieldName::Extension(p) => write!(f, "[{}]", p),
+            ProtobufConstantMessageFieldName::AnyTypeUrl(a) => write!(f, "[{}]", a),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+pub(crate) struct ProtobufConstantMessage {
+    pub(crate) fields: IndexMap<ProtobufConstantMessageFieldName, ProtobufConstant>,
+}
+
+/// constant = fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) |
+//                 strLit | boolLit
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) enum ProtobufConstant {
+    U64(u64),
+    I64(i64),
+    F64(f64), // TODO: eq
+    Bool(bool),
+    Ident(ProtobufPath),
+    String(StrLit),
+    Message(ProtobufConstantMessage),
+}
+
+impl fmt::Display for ProtobufConstant {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProtobufConstant::U64(v) => write!(f, "{}", v),
+            ProtobufConstant::I64(v) => write!(f, "{}", v),
+            ProtobufConstant::F64(v) => write!(f, "{}", format_protobuf_float(*v)),
+            ProtobufConstant::Bool(v) => write!(f, "{}", v),
+            ProtobufConstant::Ident(v) => write!(f, "{}", v),
+            ProtobufConstant::String(v) => write!(f, "{}", v),
+            // TODO: text format explicitly
+            ProtobufConstant::Message(v) => write!(f, "{:?}", v),
+        }
+    }
+}
+
+impl ProtobufConstantMessage {
+    pub fn format(&self) -> String {
+        let mut s = String::new();
+        write!(s, "{{").unwrap();
+        for (n, v) in &self.fields {
+            match v {
+                ProtobufConstant::Message(m) => write!(s, "{} {}", n, m.format()).unwrap(),
+                v => write!(s, "{}: {}", n, v.format()).unwrap(),
+            }
+        }
+        write!(s, "}}").unwrap();
+        s
+    }
+}
+
+impl ProtobufConstant {
+    pub fn format(&self) -> String {
+        match *self {
+            ProtobufConstant::U64(u) => u.to_string(),
+            ProtobufConstant::I64(i) => i.to_string(),
+            ProtobufConstant::F64(f) => format_protobuf_float(f),
+            ProtobufConstant::Bool(b) => b.to_string(),
+            ProtobufConstant::Ident(ref i) => format!("{}", i),
+            ProtobufConstant::String(ref s) => s.quoted(),
+            ProtobufConstant::Message(ref s) => s.format(),
+        }
+    }
+
+    /** Interpret .proto constant as an reflection value. */
+    pub fn as_type(&self, ty: RuntimeType) -> anyhow::Result<ReflectValueBox> {
+        match (self, &ty) {
+            (ProtobufConstant::Ident(ident), RuntimeType::Enum(e)) => {
+                if let Some(v) = e.value_by_name(&ident.to_string()) {
+                    return Ok(ReflectValueBox::Enum(e.clone(), v.value()));
+                }
+            }
+            (ProtobufConstant::Bool(b), RuntimeType::Bool) => return Ok(ReflectValueBox::Bool(*b)),
+            (ProtobufConstant::String(lit), RuntimeType::String) => {
+                return Ok(ReflectValueBox::String(lit.decode_utf8()?))
+            }
+            _ => {}
+        }
+        Err(ModelError::InconvertibleValue(ty.clone(), self.clone()).into())
+    }
+}
+
+/// Equivalent of `UninterpretedOption.NamePart`.
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) enum ProtobufOptionNamePart {
+    Direct(ProtobufIdent),
+    Ext(ProtobufPath),
+}
+
+impl fmt::Display for ProtobufOptionNamePart {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProtobufOptionNamePart::Direct(n) => write!(f, "{}", n),
+            ProtobufOptionNamePart::Ext(n) => write!(f, "({})", n),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct ProtobufOptionNameExt(pub Vec<ProtobufOptionNamePart>);
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) enum ProtobufOptionName {
+    Builtin(ProtobufIdent),
+    Ext(ProtobufOptionNameExt),
+}
+
+impl ProtobufOptionName {
+    pub fn simple(name: &str) -> ProtobufOptionName {
+        ProtobufOptionName::Builtin(ProtobufIdent::new(name))
+    }
+}
+
+impl fmt::Display for ProtobufOptionNameExt {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for (index, comp) in self.0.iter().enumerate() {
+            if index != 0 {
+                write!(f, ".")?;
+            }
+            write!(f, "{}", comp)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for ProtobufOptionName {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ProtobufOptionName::Builtin(n) => write!(f, "{}", n),
+            ProtobufOptionName::Ext(n) => write!(f, "{}", n),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct ProtobufOption {
+    pub name: ProtobufOptionName,
+    pub value: ProtobufConstant,
+}
+
+/// Visibility of import statement
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub(crate) enum ImportVis {
+    Default,
+    Public,
+    Weak,
+}
+
+impl Default for ImportVis {
+    fn default() -> Self {
+        ImportVis::Default
+    }
+}
+
+/// Import statement
+#[derive(Debug, Default, Clone)]
+pub(crate) struct Import {
+    pub path: ProtoPathBuf,
+    pub vis: ImportVis,
+}
+
+/// A File descriptor representing a whole .proto file
+#[derive(Debug, Default, Clone)]
+pub(crate) struct FileDescriptor {
+    /// Imports
+    pub imports: Vec<Import>,
+    /// Package
+    pub package: ProtobufAbsPath,
+    /// Protobuf Syntax
+    pub syntax: Syntax,
+    /// Top level messages
+    pub messages: Vec<WithLoc<Message>>,
+    /// Enums
+    pub enums: Vec<WithLoc<Enumeration>>,
+    /// Extensions
+    pub extensions: Vec<WithLoc<Extension>>,
+    /// Services
+    pub services: Vec<WithLoc<Service>>,
+    /// Non-builtin options
+    pub options: Vec<ProtobufOption>,
+}
+
+impl FileDescriptor {
+    /// Parses a .proto file content into a `FileDescriptor`
+    pub fn parse<S: AsRef<str>>(file: S) -> Result<Self, ParserErrorWithLocation> {
+        let mut parser = Parser::new(file.as_ref());
+        match parser.next_proto() {
+            Ok(r) => Ok(r),
+            Err(error) => {
+                let Loc { line, col } = parser.tokenizer.loc();
+                Err(ParserErrorWithLocation { error, line, col })
+            }
+        }
+    }
+}
diff --git a/src/pure/parse_and_typecheck.rs b/src/pure/parse_and_typecheck.rs
new file mode 100644
index 0000000..4101c4b
--- /dev/null
+++ b/src/pure/parse_and_typecheck.rs
@@ -0,0 +1,350 @@
+use std::fmt;
+use std::fs;
+use std::io;
+use std::path::Path;
+use std::path::PathBuf;
+use std::str;
+
+use indexmap::IndexMap;
+use protobuf::descriptor::FileDescriptorProto;
+use protobuf::reflect::FileDescriptor;
+
+use crate::parse_and_typecheck::ParsedAndTypechecked;
+use crate::proto;
+use crate::proto_path::ProtoPath;
+use crate::proto_path::ProtoPathBuf;
+use crate::pure::convert;
+use crate::pure::model;
+use crate::FileDescriptorPair;
+use crate::Parser;
+
+#[derive(Debug, thiserror::Error)]
+enum ParseAndTypeckError {
+    #[error("file `{0}` content is not UTF-8")]
+    FileContentIsNotUtf8(String),
+    #[error("protobuf path `{0}` is not found in import path {1}")]
+    FileNotFoundInImportPath(String, String),
+    #[error("file `{0}` must reside in include path {1}")]
+    FileMustResideInImportPath(String, String),
+    #[error("could not read file `{0}`: {1}")]
+    CouldNotReadFile(String, io::Error),
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("error in `{file}`: {error}")]
+struct WithFileError {
+    file: String,
+    #[source]
+    error: anyhow::Error,
+}
+
+/// Resolve `.proto` files. `Display` is used for error messages.
+pub trait ProtoPathResolver: fmt::Display {
+    /// Resolve a `.proto` file.
+    ///
+    /// Return `None` if a path is unknown, and if a path is a built-in protobuf file,
+    /// like `google/protobuf/descriptor.proto`, it will be handled by the library.
+    fn resolve(&self, path: &ProtoPath) -> anyhow::Result<Option<ResolvedProtoFile>>;
+}
+
+struct Run<R>
+where
+    R: ProtoPathResolver,
+{
+    parsed_files: IndexMap<ProtoPathBuf, FileDescriptorPair>,
+    resolver: R,
+}
+
+impl<R> Run<R>
+where
+    R: ProtoPathResolver,
+{
+    fn file_and_all_deps_already_parsed(
+        &self,
+        protobuf_path: &ProtoPath,
+        result: &mut IndexMap<ProtoPathBuf, FileDescriptorPair>,
+    ) {
+        if let Some(_) = result.get(protobuf_path) {
+            return;
+        }
+
+        let pair = self
+            .parsed_files
+            .get(protobuf_path)
+            .expect("must be already parsed");
+        result.insert(protobuf_path.to_proto_path_buf(), pair.clone());
+
+        self.all_deps_already_parsed(&pair.parsed, result);
+    }
+
+    fn all_deps_already_parsed(
+        &self,
+        parsed: &model::FileDescriptor,
+        result: &mut IndexMap<ProtoPathBuf, FileDescriptorPair>,
+    ) {
+        for import in &parsed.imports {
+            self.file_and_all_deps_already_parsed(&import.path, result);
+        }
+    }
+
+    fn add_file_content(
+        &mut self,
+        protobuf_path: &ProtoPath,
+        resolved: &ResolvedProtoFile,
+    ) -> anyhow::Result<()> {
+        let content = str::from_utf8(&resolved.content)
+            .map_err(|_| ParseAndTypeckError::FileContentIsNotUtf8(protobuf_path.to_string()))?;
+
+        let parsed = model::FileDescriptor::parse(&content).map_err(|e| WithFileError {
+            file: resolved.path.clone(),
+            error: e.into(),
+        })?;
+
+        for import in &parsed.imports {
+            self.add_imported_file(&import.path)?;
+        }
+
+        let mut this_file_deps = IndexMap::new();
+        self.all_deps_already_parsed(&parsed, &mut this_file_deps);
+
+        let this_file_deps: Vec<_> = this_file_deps.into_iter().map(|(_, v)| v).collect();
+
+        let descriptor_proto = convert::file_descriptor(protobuf_path, &parsed, &this_file_deps)
+            .map_err(|e| WithFileError {
+                file: resolved.path.clone(),
+                error: e.into(),
+            })?;
+
+        let deps: Vec<FileDescriptor> = self
+            .parsed_files
+            .values()
+            .map(|v| v.descriptor.clone())
+            .collect();
+        let descriptor = FileDescriptor::new_dynamic(descriptor_proto.clone(), &deps)?;
+
+        self.parsed_files.insert(
+            protobuf_path.to_proto_path_buf(),
+            FileDescriptorPair {
+                parsed,
+                descriptor_proto,
+                descriptor,
+            },
+        );
+
+        Ok(())
+    }
+
+    fn add_imported_file(&mut self, protobuf_path: &ProtoPath) -> anyhow::Result<()> {
+        if let Some(_) = self.parsed_files.get(protobuf_path) {
+            return Ok(());
+        }
+
+        let resolved = self.resolver.resolve(protobuf_path)?;
+        if let Some(resolved) = resolved {
+            return self.add_file_content(protobuf_path, &resolved);
+        }
+
+        let embedded = match protobuf_path.to_str() {
+            "rustproto.proto" => Some(proto::RUSTPROTO_PROTO),
+            "google/protobuf/any.proto" => Some(proto::ANY_PROTO),
+            "google/protobuf/api.proto" => Some(proto::API_PROTO),
+            "google/protobuf/descriptor.proto" => Some(proto::DESCRIPTOR_PROTO),
+            "google/protobuf/duration.proto" => Some(proto::DURATION_PROTO),
+            "google/protobuf/empty.proto" => Some(proto::EMPTY_PROTO),
+            "google/protobuf/field_mask.proto" => Some(proto::FIELD_MASK_PROTO),
+            "google/protobuf/source_context.proto" => Some(proto::SOURCE_CONTEXT_PROTO),
+            "google/protobuf/struct.proto" => Some(proto::STRUCT_PROTO),
+            "google/protobuf/timestamp.proto" => Some(proto::TIMESTAMP_PROTO),
+            "google/protobuf/type.proto" => Some(proto::TYPE_PROTO),
+            "google/protobuf/wrappers.proto" => Some(proto::WRAPPERS_PROTO),
+            _ => None,
+        };
+
+        match embedded {
+            Some(content) => self.add_file_content(
+                protobuf_path,
+                &ResolvedProtoFile {
+                    path: protobuf_path.to_string(),
+                    content: content.as_bytes().to_vec(),
+                },
+            ),
+            None => Err(ParseAndTypeckError::FileNotFoundInImportPath(
+                protobuf_path.to_string(),
+                format!("{}", self.resolver),
+            )
+            .into()),
+        }
+    }
+}
+
+pub(crate) fn path_to_proto_path(
+    path: &Path,
+    includes: &[PathBuf],
+) -> anyhow::Result<ProtoPathBuf> {
+    for include in includes {
+        if include == Path::new(".") && path.is_relative() {
+            // Special handling of `.` to allow using `.` as an include path
+            // and `foo.proto` as input.
+            return ProtoPathBuf::from_path(path);
+        }
+        match path.strip_prefix(include) {
+            Ok(stripped) => return ProtoPathBuf::from_path(stripped),
+            Err(_) => continue,
+        }
+    }
+    Err(ParseAndTypeckError::FileMustResideInImportPath(
+        path.display().to_string(),
+        format!("{:?}", includes),
+    )
+    .into())
+}
+
+/// `.proto` file result provided from the [`ProtoPathResolver`].
+pub struct ResolvedProtoFile {
+    /// For error reporting.
+    pub path: String,
+    /// File content.
+    pub content: Vec<u8>,
+}
+
+fn fs_resolver(includes: &[PathBuf]) -> impl ProtoPathResolver {
+    struct Impl {
+        includes: Vec<PathBuf>,
+    }
+
+    impl fmt::Display for Impl {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            write!(f, "{:?}", self.includes)
+        }
+    }
+
+    impl ProtoPathResolver for Impl {
+        fn resolve(&self, proto_path: &ProtoPath) -> anyhow::Result<Option<ResolvedProtoFile>> {
+            for include_dir in &self.includes {
+                let fs_path = include_dir.join(proto_path.to_path());
+                match fs::read_to_string(&fs_path) {
+                    Ok(content) => {
+                        return Ok(Some(ResolvedProtoFile {
+                            path: fs_path.display().to_string(),
+                            content: content.into_bytes(),
+                        }))
+                    }
+                    Err(e) if e.kind() == io::ErrorKind::NotFound => continue,
+                    Err(e) => {
+                        return Err(ParseAndTypeckError::CouldNotReadFile(
+                            fs_path.display().to_string(),
+                            e,
+                        )
+                        .into())
+                    }
+                }
+            }
+            Ok(None)
+        }
+    }
+
+    Impl {
+        includes: includes.to_vec(),
+    }
+}
+
+/// Parse `.proto` files using pure Rust implementation.
+pub fn parse_and_typecheck(parser: &Parser) -> anyhow::Result<ParsedAndTypechecked> {
+    let mut run = Run {
+        parsed_files: IndexMap::new(),
+        resolver: fs_resolver(&parser.includes),
+    };
+
+    let relative_paths = parser
+        .inputs
+        .iter()
+        .map(|input| Ok((path_to_proto_path(input, &parser.includes)?, input)))
+        .collect::<anyhow::Result<Vec<_>>>()?;
+
+    for (proto_path, path) in &relative_paths {
+        let content = fs::read_to_string(path)
+            .map_err(|e| ParseAndTypeckError::CouldNotReadFile(path.display().to_string(), e))?;
+        run.add_file_content(
+            proto_path,
+            &ResolvedProtoFile {
+                path: path.display().to_string(),
+                content: content.into_bytes(),
+            },
+        )?;
+    }
+
+    let file_descriptors: Vec<_> = run
+        .parsed_files
+        .into_iter()
+        .map(|(_, v)| v.descriptor_proto)
+        .collect();
+
+    Ok(ParsedAndTypechecked {
+        relative_paths: relative_paths.into_iter().map(|(p, _)| p).collect(),
+        file_descriptors,
+        parser: "pure".to_owned(),
+    })
+}
+
+/// TODO: this API is to be refactored.
+pub fn parse_and_typecheck_custom(
+    input: &[ProtoPathBuf],
+    resolver: impl ProtoPathResolver,
+) -> anyhow::Result<Vec<FileDescriptorProto>> {
+    let mut run = Run {
+        parsed_files: IndexMap::new(),
+        resolver,
+    };
+
+    for proto_path in input {
+        run.add_imported_file(proto_path)?;
+    }
+
+    Ok(run
+        .parsed_files
+        .into_iter()
+        .map(|(_, v)| v.descriptor_proto)
+        .collect())
+}
+
+#[cfg(test)]
+mod test {
+    use std::fmt;
+
+    use crate::proto_path::ProtoPath;
+    use crate::pure::parse_and_typecheck::ProtoPathResolver;
+    use crate::pure::parse_and_typecheck::ResolvedProtoFile;
+    use crate::ProtoPathBuf;
+
+    #[test]
+    fn parse_and_typecheck_custom() {
+        struct ResolverImpl;
+
+        impl fmt::Display for ResolverImpl {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                write!(f, "ResolverImpl")
+            }
+        }
+
+        impl ProtoPathResolver for ResolverImpl {
+            fn resolve(&self, proto_path: &ProtoPath) -> anyhow::Result<Option<ResolvedProtoFile>> {
+                if proto_path == "xx.proto" {
+                    Ok(Some(ResolvedProtoFile {
+                        path: "xx.proto".to_string(),
+                        content: "syntax = 'proto3'; message Foo {}".as_bytes().to_vec(),
+                    }))
+                } else {
+                    Ok(None)
+                }
+            }
+        }
+
+        let resolved = super::parse_and_typecheck_custom(
+            &[ProtoPathBuf::new("xx.proto".to_owned()).unwrap()],
+            ResolverImpl,
+        )
+        .unwrap();
+        assert_eq!(1, resolved.len());
+        assert_eq!("Foo", resolved[0].message_type[0].name());
+    }
+}
diff --git a/src/pure/parse_dependencies.rs b/src/pure/parse_dependencies.rs
new file mode 100644
index 0000000..4be164d
--- /dev/null
+++ b/src/pure/parse_dependencies.rs
@@ -0,0 +1,41 @@
+use protobuf::descriptor::FileDescriptorProto;
+
+use crate::pure::convert::populate_dependencies;
+use crate::pure::model;
+use crate::pure::parser::ParserErrorWithLocation;
+
+/// Parse imports from a `.proto` file.
+///
+/// The result is [`FileDescriptorProto`] object with only `*dependency` fields filled.
+pub fn parse_dependencies(content: &str) -> Result<FileDescriptorProto, ParserErrorWithLocation> {
+    let input = model::FileDescriptor::parse(content)?;
+    let mut output = FileDescriptorProto::new();
+    populate_dependencies(&input, &mut output);
+    Ok(output)
+}
+
+#[cfg(test)]
+mod test {
+    #[test]
+    fn parse_dependencies() {
+        let deps = crate::pure::parse_dependencies::parse_dependencies(
+            r"
+syntax = 'proto3';
+
+import 'google/protobuf/field_mask.proto';
+import public 'google/protobuf/struct.proto';
+
+message IgnoreMe {}
+",
+        )
+        .unwrap();
+        assert_eq!(
+            &[
+                "google/protobuf/field_mask.proto",
+                "google/protobuf/struct.proto",
+            ],
+            &deps.dependency[..]
+        );
+        assert_eq!(&[1], &deps.public_dependency[..]);
+    }
+}
diff --git a/src/pure/parser.rs b/src/pure/parser.rs
new file mode 100644
index 0000000..1c40f40
--- /dev/null
+++ b/src/pure/parser.rs
@@ -0,0 +1,1582 @@
+use std::str;
+
+use protobuf_support::lexer::int;
+use protobuf_support::lexer::lexer_impl::LexerError;
+use protobuf_support::lexer::num_lit::NumLit;
+use protobuf_support::lexer::parser_language::ParserLanguage;
+use protobuf_support::lexer::str_lit::StrLitDecodeError;
+use protobuf_support::lexer::token::Token;
+use protobuf_support::lexer::tokenizer::Tokenizer;
+use protobuf_support::lexer::tokenizer::TokenizerError;
+
+use crate::model::AnyTypeUrl;
+use crate::model::ProtobufConstantMessageFieldName;
+use crate::proto_path::ProtoPathBuf;
+use crate::protobuf_abs_path::ProtobufAbsPath;
+use crate::protobuf_ident::ProtobufIdent;
+use crate::protobuf_path::ProtobufPath;
+use crate::protobuf_rel_path::ProtobufRelPath;
+use crate::pure::model;
+use crate::pure::model::EnumValue;
+use crate::pure::model::Enumeration;
+use crate::pure::model::Extension;
+use crate::pure::model::Field;
+use crate::pure::model::FieldNumberRange;
+use crate::pure::model::FieldOrOneOf;
+use crate::pure::model::FieldType;
+use crate::pure::model::FileDescriptor;
+use crate::pure::model::Group;
+use crate::pure::model::ImportVis;
+use crate::pure::model::Message;
+use crate::pure::model::Method;
+use crate::pure::model::OneOf;
+use crate::pure::model::ProtobufConstant;
+use crate::pure::model::ProtobufConstantMessage;
+use crate::pure::model::ProtobufOption;
+use crate::pure::model::ProtobufOptionName;
+use crate::pure::model::ProtobufOptionNameExt;
+use crate::pure::model::ProtobufOptionNamePart;
+use crate::pure::model::Rule;
+use crate::pure::model::Service;
+use crate::pure::model::Syntax;
+use crate::pure::model::WithLoc;
+
+/// Basic information about parsing error.
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum ParserError {
+    #[error("{0}")]
+    TokenizerError(#[source] TokenizerError),
+    // TODO
+    #[error("incorrect input")]
+    IncorrectInput,
+    #[error("not UTF-8")]
+    NotUtf8,
+    #[error("expecting a constant")]
+    ExpectConstant,
+    #[error("unknown syntax")]
+    UnknownSyntax,
+    #[error("integer overflow")]
+    IntegerOverflow,
+    #[error("label not allowed")]
+    LabelNotAllowed,
+    #[error("label required")]
+    LabelRequired,
+    #[error("group name should start with upper case")]
+    GroupNameShouldStartWithUpperCase,
+    #[error("map field not allowed")]
+    MapFieldNotAllowed,
+    #[error("string literal decode error: {0}")]
+    StrLitDecodeError(#[source] StrLitDecodeError),
+    #[error("lexer error: {0}")]
+    LexerError(#[source] LexerError),
+    #[error("oneof in group")]
+    OneOfInGroup,
+    #[error("oneof in oneof")]
+    OneOfInOneOf,
+    #[error("oneof in extend")]
+    OneOfInExtend,
+}
+
+impl From<TokenizerError> for ParserError {
+    fn from(e: TokenizerError) -> Self {
+        ParserError::TokenizerError(e)
+    }
+}
+
+impl From<StrLitDecodeError> for ParserError {
+    fn from(e: StrLitDecodeError) -> Self {
+        ParserError::StrLitDecodeError(e)
+    }
+}
+
+impl From<LexerError> for ParserError {
+    fn from(e: LexerError) -> Self {
+        ParserError::LexerError(e)
+    }
+}
+
+impl From<int::Overflow> for ParserError {
+    fn from(_: int::Overflow) -> Self {
+        ParserError::IntegerOverflow
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("at {line}:{col}: {error}")]
+pub struct ParserErrorWithLocation {
+    #[source]
+    pub error: anyhow::Error,
+    /// 1-based
+    pub line: u32,
+    /// 1-based
+    pub col: u32,
+}
+
+trait ToU8 {
+    fn to_u8(&self) -> anyhow::Result<u8>;
+}
+
+trait ToI32 {
+    fn to_i32(&self) -> anyhow::Result<i32>;
+}
+
+trait ToI64 {
+    fn to_i64(&self) -> anyhow::Result<i64>;
+}
+
+trait ToChar {
+    fn to_char(&self) -> anyhow::Result<char>;
+}
+
+impl ToI32 for u64 {
+    fn to_i32(&self) -> anyhow::Result<i32> {
+        if *self <= i32::max_value() as u64 {
+            Ok(*self as i32)
+        } else {
+            Err(ParserError::IntegerOverflow.into())
+        }
+    }
+}
+
+impl ToI32 for i64 {
+    fn to_i32(&self) -> anyhow::Result<i32> {
+        if *self <= i32::max_value() as i64 && *self >= i32::min_value() as i64 {
+            Ok(*self as i32)
+        } else {
+            Err(ParserError::IntegerOverflow.into())
+        }
+    }
+}
+
+impl ToI64 for u64 {
+    fn to_i64(&self) -> anyhow::Result<i64> {
+        if *self <= i64::max_value() as u64 {
+            Ok(*self as i64)
+        } else {
+            Err(ParserError::IntegerOverflow.into())
+        }
+    }
+}
+
+impl ToChar for u8 {
+    fn to_char(&self) -> anyhow::Result<char> {
+        if *self <= 0x7f {
+            Ok(*self as char)
+        } else {
+            Err(ParserError::NotUtf8.into())
+        }
+    }
+}
+
+impl ToU8 for u32 {
+    fn to_u8(&self) -> anyhow::Result<u8> {
+        if *self as u8 as u32 == *self {
+            Ok(*self as u8)
+        } else {
+            Err(ParserError::IntegerOverflow.into())
+        }
+    }
+}
+
+#[derive(Clone)]
+pub(crate) struct Parser<'a> {
+    pub tokenizer: Tokenizer<'a>,
+    syntax: Syntax,
+}
+
+#[derive(Copy, Clone)]
+enum MessageBodyParseMode {
+    MessageProto2,
+    MessageProto3,
+    Oneof,
+    ExtendProto2,
+    ExtendProto3,
+}
+
+impl MessageBodyParseMode {
+    fn label_allowed(&self, label: Rule) -> bool {
+        match label {
+            Rule::Repeated => match *self {
+                MessageBodyParseMode::MessageProto2
+                | MessageBodyParseMode::MessageProto3
+                | MessageBodyParseMode::ExtendProto2
+                | MessageBodyParseMode::ExtendProto3 => true,
+                MessageBodyParseMode::Oneof => false,
+            },
+            Rule::Optional => match *self {
+                MessageBodyParseMode::MessageProto2 | MessageBodyParseMode::ExtendProto2 => true,
+                MessageBodyParseMode::MessageProto3 | MessageBodyParseMode::ExtendProto3 => true,
+                MessageBodyParseMode::Oneof => false,
+            },
+            Rule::Required => match *self {
+                MessageBodyParseMode::MessageProto2 | MessageBodyParseMode::ExtendProto2 => true,
+                MessageBodyParseMode::MessageProto3 | MessageBodyParseMode::ExtendProto3 => false,
+                MessageBodyParseMode::Oneof => false,
+            },
+        }
+    }
+
+    fn some_label_required(&self) -> bool {
+        match *self {
+            MessageBodyParseMode::MessageProto2 | MessageBodyParseMode::ExtendProto2 => true,
+            MessageBodyParseMode::MessageProto3
+            | MessageBodyParseMode::ExtendProto3
+            | MessageBodyParseMode::Oneof => false,
+        }
+    }
+
+    fn map_allowed(&self) -> bool {
+        match *self {
+            MessageBodyParseMode::MessageProto2
+            | MessageBodyParseMode::MessageProto3
+            | MessageBodyParseMode::ExtendProto2
+            | MessageBodyParseMode::ExtendProto3 => true,
+            MessageBodyParseMode::Oneof => false,
+        }
+    }
+
+    fn is_most_non_fields_allowed(&self) -> bool {
+        match *self {
+            MessageBodyParseMode::MessageProto2 | MessageBodyParseMode::MessageProto3 => true,
+            MessageBodyParseMode::ExtendProto2
+            | MessageBodyParseMode::ExtendProto3
+            | MessageBodyParseMode::Oneof => false,
+        }
+    }
+
+    fn is_option_allowed(&self) -> bool {
+        match *self {
+            MessageBodyParseMode::MessageProto2
+            | MessageBodyParseMode::MessageProto3
+            | MessageBodyParseMode::Oneof => true,
+            MessageBodyParseMode::ExtendProto2 | MessageBodyParseMode::ExtendProto3 => false,
+        }
+    }
+
+    fn is_extensions_allowed(&self) -> bool {
+        match self {
+            MessageBodyParseMode::MessageProto2 => true,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Default)]
+pub(crate) struct MessageBody {
+    pub fields: Vec<WithLoc<FieldOrOneOf>>,
+    pub reserved_nums: Vec<FieldNumberRange>,
+    pub reserved_names: Vec<String>,
+    pub messages: Vec<WithLoc<Message>>,
+    pub enums: Vec<WithLoc<Enumeration>>,
+    pub options: Vec<ProtobufOption>,
+    pub extension_ranges: Vec<FieldNumberRange>,
+    pub extensions: Vec<WithLoc<Extension>>,
+}
+
+trait NumLitEx {
+    fn to_option_value(&self, sign_is_plus: bool) -> anyhow::Result<ProtobufConstant>;
+}
+
+impl NumLitEx for NumLit {
+    fn to_option_value(&self, sign_is_plus: bool) -> anyhow::Result<ProtobufConstant> {
+        Ok(match (*self, sign_is_plus) {
+            (NumLit::U64(u), true) => ProtobufConstant::U64(u),
+            (NumLit::F64(f), true) => ProtobufConstant::F64(f),
+            (NumLit::U64(u), false) => {
+                ProtobufConstant::I64(int::neg(u).map_err(|_| ParserError::IntegerOverflow)?)
+            }
+            (NumLit::F64(f), false) => ProtobufConstant::F64(-f),
+        })
+    }
+}
+
+impl<'a> Parser<'a> {
+    pub(crate) fn new(input: &'a str) -> Parser<'a> {
+        Parser {
+            tokenizer: Tokenizer::new(input, ParserLanguage::Proto),
+            syntax: Syntax::Proto2,
+        }
+    }
+
+    // Protobuf grammar
+
+    // fullIdent = ident { "." ident }
+    fn next_full_ident(&mut self) -> anyhow::Result<ProtobufPath> {
+        let mut full_ident = String::new();
+        // https://github.com/google/protobuf/issues/4563
+        if self.tokenizer.next_symbol_if_eq('.')? {
+            full_ident.push('.');
+        }
+        full_ident.push_str(&self.tokenizer.next_ident()?);
+        while self.tokenizer.next_symbol_if_eq('.')? {
+            full_ident.push('.');
+            full_ident.push_str(&self.tokenizer.next_ident()?);
+        }
+        Ok(ProtobufPath::new(full_ident))
+    }
+
+    // fullIdent = ident { "." ident }
+    fn next_full_ident_rel(&mut self) -> anyhow::Result<ProtobufRelPath> {
+        let mut full_ident = String::new();
+        full_ident.push_str(&self.tokenizer.next_ident()?);
+        while self.tokenizer.next_symbol_if_eq('.')? {
+            full_ident.push('.');
+            full_ident.push_str(&self.tokenizer.next_ident()?);
+        }
+        Ok(ProtobufRelPath::new(full_ident))
+    }
+
+    // emptyStatement = ";"
+    fn next_empty_statement_opt(&mut self) -> anyhow::Result<Option<()>> {
+        if self.tokenizer.next_symbol_if_eq(';')? {
+            Ok(Some(()))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // messageName = ident
+    // enumName = ident
+    // messageType = [ "." ] { ident "." } messageName
+    // enumType = [ "." ] { ident "." } enumName
+    fn next_message_or_enum_type(&mut self) -> anyhow::Result<ProtobufPath> {
+        self.next_full_ident()
+    }
+
+    // groupName = capitalLetter { letter | decimalDigit | "_" }
+    fn next_group_name(&mut self) -> anyhow::Result<String> {
+        // lexer cannot distinguish between group name and other ident
+        let mut clone = self.clone();
+        let ident = clone.tokenizer.next_ident()?;
+        if !ident.chars().next().unwrap().is_ascii_uppercase() {
+            return Err(ParserError::GroupNameShouldStartWithUpperCase.into());
+        }
+        *self = clone;
+        Ok(ident)
+    }
+
+    // Boolean
+
+    // boolLit = "true" | "false"
+    fn next_bool_lit_opt(&mut self) -> anyhow::Result<Option<bool>> {
+        Ok(if self.tokenizer.next_ident_if_eq("true")? {
+            Some(true)
+        } else if self.tokenizer.next_ident_if_eq("false")? {
+            Some(false)
+        } else {
+            None
+        })
+    }
+
+    // Constant
+
+    fn next_num_lit(&mut self) -> anyhow::Result<NumLit> {
+        self.tokenizer
+            .next_token_check_map(|token| Ok(token.to_num_lit()?))
+    }
+
+    fn next_message_constant_field_name(
+        &mut self,
+    ) -> anyhow::Result<ProtobufConstantMessageFieldName> {
+        if self.tokenizer.next_symbol_if_eq('[')? {
+            let n = self.next_full_ident()?;
+            if self.tokenizer.next_symbol_if_eq('/')? {
+                let prefix = format!("{}", n);
+                let full_type_name = self.next_full_ident()?;
+                self.tokenizer
+                    .next_symbol_expect_eq(']', "message constant")?;
+                Ok(ProtobufConstantMessageFieldName::AnyTypeUrl(AnyTypeUrl {
+                    prefix,
+                    full_type_name,
+                }))
+            } else {
+                self.tokenizer
+                    .next_symbol_expect_eq(']', "message constant")?;
+                Ok(ProtobufConstantMessageFieldName::Extension(n))
+            }
+        } else {
+            let n = self.tokenizer.next_ident()?;
+            Ok(ProtobufConstantMessageFieldName::Regular(n))
+        }
+    }
+
+    fn next_message_constant(&mut self) -> anyhow::Result<ProtobufConstantMessage> {
+        let mut r = ProtobufConstantMessage::default();
+        self.tokenizer
+            .next_symbol_expect_eq('{', "message constant")?;
+        while !self.tokenizer.lookahead_is_symbol('}')? {
+            let n = self.next_message_constant_field_name()?;
+            let v = self.next_field_value()?;
+            r.fields.insert(n, v);
+        }
+        self.tokenizer
+            .next_symbol_expect_eq('}', "message constant")?;
+        Ok(r)
+    }
+
+    // constant = fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) |
+    //            strLit | boolLit
+    fn next_constant(&mut self) -> anyhow::Result<ProtobufConstant> {
+        // https://github.com/google/protobuf/blob/a21f225824e994ebd35e8447382ea4e0cd165b3c/src/google/protobuf/unittest_custom_options.proto#L350
+        if self.tokenizer.lookahead_is_symbol('{')? {
+            return Ok(ProtobufConstant::Message(self.next_message_constant()?));
+        }
+
+        if let Some(b) = self.next_bool_lit_opt()? {
+            return Ok(ProtobufConstant::Bool(b));
+        }
+
+        if let &Token::Symbol(c) = self.tokenizer.lookahead_some()? {
+            if c == '+' || c == '-' {
+                self.tokenizer.advance()?;
+                let sign = c == '+';
+                return Ok(self.next_num_lit()?.to_option_value(sign)?);
+            }
+        }
+
+        if let Some(r) = self.tokenizer.next_token_if_map(|token| match token {
+            &Token::StrLit(ref s) => Some(ProtobufConstant::String(s.clone())),
+            _ => None,
+        })? {
+            return Ok(r);
+        }
+
+        match self.tokenizer.lookahead_some()? {
+            &Token::IntLit(..) | &Token::FloatLit(..) => {
+                return self.next_num_lit()?.to_option_value(true);
+            }
+            &Token::Ident(..) => {
+                return Ok(ProtobufConstant::Ident(self.next_full_ident()?));
+            }
+            _ => {}
+        }
+
+        Err(ParserError::ExpectConstant.into())
+    }
+
+    fn next_field_value(&mut self) -> anyhow::Result<ProtobufConstant> {
+        if self.tokenizer.next_symbol_if_eq(':')? {
+            // Colon is optional when reading message constant.
+            self.next_constant()
+        } else {
+            Ok(ProtobufConstant::Message(self.next_message_constant()?))
+        }
+    }
+
+    fn next_int_lit(&mut self) -> anyhow::Result<u64> {
+        self.tokenizer.next_token_check_map(|token| match token {
+            &Token::IntLit(i) => Ok(i),
+            _ => Err(ParserError::IncorrectInput.into()),
+        })
+    }
+
+    // Syntax
+
+    // syntax = "syntax" "=" quote "proto2" quote ";"
+    // syntax = "syntax" "=" quote "proto3" quote ";"
+    fn next_syntax(&mut self) -> anyhow::Result<Option<Syntax>> {
+        if self.tokenizer.next_ident_if_eq("syntax")? {
+            self.tokenizer.next_symbol_expect_eq('=', "syntax")?;
+            let syntax_str = self.tokenizer.next_str_lit()?.decode_utf8()?;
+            let syntax = if syntax_str == "proto2" {
+                Syntax::Proto2
+            } else if syntax_str == "proto3" {
+                Syntax::Proto3
+            } else {
+                return Err(ParserError::UnknownSyntax.into());
+            };
+            self.tokenizer.next_symbol_expect_eq(';', "syntax")?;
+            Ok(Some(syntax))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Import Statement
+
+    // import = "import" [ "weak" | "public" ] strLit ";"
+    fn next_import_opt(&mut self) -> anyhow::Result<Option<model::Import>> {
+        if self.tokenizer.next_ident_if_eq("import")? {
+            let vis = if self.tokenizer.next_ident_if_eq("weak")? {
+                ImportVis::Weak
+            } else if self.tokenizer.next_ident_if_eq("public")? {
+                ImportVis::Public
+            } else {
+                ImportVis::Default
+            };
+            let path = self.tokenizer.next_str_lit()?.decode_utf8()?;
+            self.tokenizer.next_symbol_expect_eq(';', "import")?;
+            let path = ProtoPathBuf::new(path)?;
+            Ok(Some(model::Import { path, vis }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Package
+
+    // package = "package" fullIdent ";"
+    fn next_package_opt(&mut self) -> anyhow::Result<Option<ProtobufAbsPath>> {
+        if self.tokenizer.next_ident_if_eq("package")? {
+            let package = self.next_full_ident_rel()?;
+            self.tokenizer.next_symbol_expect_eq(';', "package")?;
+            Ok(Some(package.into_absolute()))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Option
+
+    fn next_ident(&mut self) -> anyhow::Result<ProtobufIdent> {
+        Ok(ProtobufIdent::from(self.tokenizer.next_ident()?))
+    }
+
+    fn next_option_name_component(&mut self) -> anyhow::Result<ProtobufOptionNamePart> {
+        if self.tokenizer.next_symbol_if_eq('(')? {
+            let comp = self.next_full_ident()?;
+            self.tokenizer
+                .next_symbol_expect_eq(')', "option name component")?;
+            Ok(ProtobufOptionNamePart::Ext(comp))
+        } else {
+            Ok(ProtobufOptionNamePart::Direct(self.next_ident()?))
+        }
+    }
+
+    // https://github.com/google/protobuf/issues/4563
+    // optionName = ( ident | "(" fullIdent ")" ) { "." ident }
+    fn next_option_name(&mut self) -> anyhow::Result<ProtobufOptionName> {
+        let mut components = Vec::new();
+        components.push(self.next_option_name_component()?);
+        while self.tokenizer.next_symbol_if_eq('.')? {
+            components.push(self.next_option_name_component()?);
+        }
+        if components.len() == 1 {
+            if let ProtobufOptionNamePart::Direct(n) = &components[0] {
+                return Ok(ProtobufOptionName::Builtin(n.clone()));
+            }
+        }
+        Ok(ProtobufOptionName::Ext(ProtobufOptionNameExt(components)))
+    }
+
+    // option = "option" optionName  "=" constant ";"
+    fn next_option_opt(&mut self) -> anyhow::Result<Option<ProtobufOption>> {
+        if self.tokenizer.next_ident_if_eq("option")? {
+            let name = self.next_option_name()?;
+            self.tokenizer.next_symbol_expect_eq('=', "option")?;
+            let value = self.next_constant()?;
+            self.tokenizer.next_symbol_expect_eq(';', "option")?;
+            Ok(Some(ProtobufOption { name, value }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Fields
+
+    // label = "required" | "optional" | "repeated"
+    fn next_label(&mut self, mode: MessageBodyParseMode) -> anyhow::Result<Option<Rule>> {
+        for rule in Rule::ALL {
+            let mut clone = self.clone();
+            if clone.tokenizer.next_ident_if_eq(rule.as_str())? {
+                if !mode.label_allowed(rule) {
+                    return Err(ParserError::LabelNotAllowed.into());
+                }
+
+                *self = clone;
+                return Ok(Some(rule));
+            }
+        }
+
+        if mode.some_label_required() {
+            Err(ParserError::LabelRequired.into())
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn next_field_type(&mut self) -> anyhow::Result<FieldType> {
+        let simple = &[
+            ("int32", FieldType::Int32),
+            ("int64", FieldType::Int64),
+            ("uint32", FieldType::Uint32),
+            ("uint64", FieldType::Uint64),
+            ("sint32", FieldType::Sint32),
+            ("sint64", FieldType::Sint64),
+            ("fixed32", FieldType::Fixed32),
+            ("sfixed32", FieldType::Sfixed32),
+            ("fixed64", FieldType::Fixed64),
+            ("sfixed64", FieldType::Sfixed64),
+            ("bool", FieldType::Bool),
+            ("string", FieldType::String),
+            ("bytes", FieldType::Bytes),
+            ("float", FieldType::Float),
+            ("double", FieldType::Double),
+        ];
+        for &(ref n, ref t) in simple {
+            if self.tokenizer.next_ident_if_eq(n)? {
+                return Ok(t.clone());
+            }
+        }
+
+        if let Some(t) = self.next_map_field_type_opt()? {
+            return Ok(t);
+        }
+
+        let message_or_enum = self.next_message_or_enum_type()?;
+        Ok(FieldType::MessageOrEnum(message_or_enum))
+    }
+
+    fn next_field_number(&mut self) -> anyhow::Result<i32> {
+        // TODO: not all integers are valid field numbers
+        self.tokenizer.next_token_check_map(|token| match token {
+            &Token::IntLit(i) => i.to_i32(),
+            _ => Err(ParserError::IncorrectInput.into()),
+        })
+    }
+
+    // fieldOption = optionName "=" constant
+    fn next_field_option(&mut self) -> anyhow::Result<ProtobufOption> {
+        let name = self.next_option_name()?;
+        self.tokenizer.next_symbol_expect_eq('=', "field option")?;
+        let value = self.next_constant()?;
+        Ok(ProtobufOption { name, value })
+    }
+
+    // fieldOptions = fieldOption { ","  fieldOption }
+    fn next_field_options(&mut self) -> anyhow::Result<Vec<ProtobufOption>> {
+        let mut options = Vec::new();
+
+        options.push(self.next_field_option()?);
+
+        while self.tokenizer.next_symbol_if_eq(',')? {
+            options.push(self.next_field_option()?);
+        }
+
+        Ok(options)
+    }
+
+    // field = label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
+    // group = label "group" groupName "=" fieldNumber messageBody
+    fn next_field(&mut self, mode: MessageBodyParseMode) -> anyhow::Result<WithLoc<Field>> {
+        let loc = self.tokenizer.lookahead_loc();
+        let rule = if self.clone().tokenizer.next_ident_if_eq("map")? {
+            if !mode.map_allowed() {
+                return Err(ParserError::MapFieldNotAllowed.into());
+            }
+            None
+        } else {
+            self.next_label(mode)?
+        };
+        if self.tokenizer.next_ident_if_eq("group")? {
+            let name = self.next_group_name()?.to_owned();
+            self.tokenizer.next_symbol_expect_eq('=', "group")?;
+            let number = self.next_field_number()?;
+
+            let mode = match self.syntax {
+                Syntax::Proto2 => MessageBodyParseMode::MessageProto2,
+                Syntax::Proto3 => MessageBodyParseMode::MessageProto3,
+            };
+
+            let MessageBody { fields, .. } = self.next_message_body(mode)?;
+
+            let fields = fields
+                .into_iter()
+                .map(|fo| match fo.t {
+                    FieldOrOneOf::Field(f) => Ok(f),
+                    FieldOrOneOf::OneOf(_) => Err(ParserError::OneOfInGroup),
+                })
+                .collect::<Result<_, ParserError>>()?;
+
+            let field = Field {
+                // The field name is a lowercased version of the type name
+                // (which has been verified to start with an uppercase letter).
+                // https://git.io/JvxAP
+                name: name.to_ascii_lowercase(),
+                rule,
+                typ: FieldType::Group(Group { name, fields }),
+                number,
+                options: Vec::new(),
+            };
+            Ok(WithLoc { t: field, loc })
+        } else {
+            let typ = self.next_field_type()?;
+            let name = self.tokenizer.next_ident()?.to_owned();
+            self.tokenizer.next_symbol_expect_eq('=', "field")?;
+            let number = self.next_field_number()?;
+
+            let mut options = Vec::new();
+
+            if self.tokenizer.next_symbol_if_eq('[')? {
+                for o in self.next_field_options()? {
+                    options.push(o);
+                }
+                self.tokenizer.next_symbol_expect_eq(']', "field")?;
+            }
+            self.tokenizer.next_symbol_expect_eq(';', "field")?;
+            let field = Field {
+                name,
+                rule,
+                typ,
+                number,
+                options,
+            };
+            Ok(WithLoc { t: field, loc })
+        }
+    }
+
+    // oneof = "oneof" oneofName "{" { oneofField | emptyStatement } "}"
+    // oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
+    fn next_oneof_opt(&mut self) -> anyhow::Result<Option<OneOf>> {
+        if self.tokenizer.next_ident_if_eq("oneof")? {
+            let name = self.tokenizer.next_ident()?.to_owned();
+            let MessageBody {
+                fields, options, ..
+            } = self.next_message_body(MessageBodyParseMode::Oneof)?;
+            let fields = fields
+                .into_iter()
+                .map(|fo| match fo.t {
+                    FieldOrOneOf::Field(f) => Ok(f),
+                    FieldOrOneOf::OneOf(_) => Err(ParserError::OneOfInOneOf),
+                })
+                .collect::<Result<_, ParserError>>()?;
+            Ok(Some(OneOf {
+                name,
+                fields,
+                options,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
+    // keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" |
+    //           "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string"
+    fn next_map_field_type_opt(&mut self) -> anyhow::Result<Option<FieldType>> {
+        if self.tokenizer.next_ident_if_eq("map")? {
+            self.tokenizer
+                .next_symbol_expect_eq('<', "map field type")?;
+            // TODO: restrict key types
+            let key = self.next_field_type()?;
+            self.tokenizer
+                .next_symbol_expect_eq(',', "map field type")?;
+            let value = self.next_field_type()?;
+            self.tokenizer
+                .next_symbol_expect_eq('>', "map field type")?;
+            Ok(Some(FieldType::Map(Box::new((key, value)))))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Extensions and Reserved
+
+    // Extensions
+
+    // range =  intLit [ "to" ( intLit | "max" ) ]
+    fn next_range(&mut self) -> anyhow::Result<FieldNumberRange> {
+        let from = self.next_field_number()?;
+        let to = if self.tokenizer.next_ident_if_eq("to")? {
+            if self.tokenizer.next_ident_if_eq("max")? {
+                0x20000000 - 1
+            } else {
+                self.next_field_number()?
+            }
+        } else {
+            from
+        };
+        Ok(FieldNumberRange { from, to })
+    }
+
+    // ranges = range { "," range }
+    fn next_ranges(&mut self) -> anyhow::Result<Vec<FieldNumberRange>> {
+        let mut ranges = Vec::new();
+        ranges.push(self.next_range()?);
+        while self.tokenizer.next_symbol_if_eq(',')? {
+            ranges.push(self.next_range()?);
+        }
+        Ok(ranges)
+    }
+
+    // extensions = "extensions" ranges ";"
+    fn next_extensions_opt(&mut self) -> anyhow::Result<Option<Vec<FieldNumberRange>>> {
+        if self.tokenizer.next_ident_if_eq("extensions")? {
+            Ok(Some(self.next_ranges()?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Reserved
+
+    // Grammar is incorrect: https://github.com/google/protobuf/issues/4558
+    // reserved = "reserved" ( ranges | fieldNames ) ";"
+    // fieldNames = fieldName { "," fieldName }
+    fn next_reserved_opt(
+        &mut self,
+    ) -> anyhow::Result<Option<(Vec<FieldNumberRange>, Vec<String>)>> {
+        if self.tokenizer.next_ident_if_eq("reserved")? {
+            let (ranges, names) = if let &Token::StrLit(..) = self.tokenizer.lookahead_some()? {
+                let mut names = Vec::new();
+                names.push(self.tokenizer.next_str_lit()?.decode_utf8()?);
+                while self.tokenizer.next_symbol_if_eq(',')? {
+                    names.push(self.tokenizer.next_str_lit()?.decode_utf8()?);
+                }
+                (Vec::new(), names)
+            } else {
+                (self.next_ranges()?, Vec::new())
+            };
+
+            self.tokenizer.next_symbol_expect_eq(';', "reserved")?;
+
+            Ok(Some((ranges, names)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Top Level definitions
+
+    // Enum definition
+
+    // enumValueOption = optionName "=" constant
+    fn next_enum_value_option(&mut self) -> anyhow::Result<ProtobufOption> {
+        let name = self.next_option_name()?;
+        self.tokenizer
+            .next_symbol_expect_eq('=', "enum value option")?;
+        let value = self.next_constant()?;
+        Ok(ProtobufOption { name, value })
+    }
+
+    // https://github.com/google/protobuf/issues/4561
+    fn next_enum_value(&mut self) -> anyhow::Result<i32> {
+        let minus = self.tokenizer.next_symbol_if_eq('-')?;
+        let lit = self.next_int_lit()?;
+        Ok(if minus {
+            let unsigned = lit.to_i64()?;
+            match unsigned.checked_neg() {
+                Some(neg) => neg.to_i32()?,
+                None => return Err(ParserError::IntegerOverflow.into()),
+            }
+        } else {
+            lit.to_i32()?
+        })
+    }
+
+    // enumField = ident "=" intLit [ "[" enumValueOption { ","  enumValueOption } "]" ]";"
+    fn next_enum_field(&mut self) -> anyhow::Result<EnumValue> {
+        let name = self.tokenizer.next_ident()?.to_owned();
+        self.tokenizer.next_symbol_expect_eq('=', "enum field")?;
+        let number = self.next_enum_value()?;
+        let mut options = Vec::new();
+        if self.tokenizer.next_symbol_if_eq('[')? {
+            options.push(self.next_enum_value_option()?);
+            while self.tokenizer.next_symbol_if_eq(',')? {
+                options.push(self.next_enum_value_option()?);
+            }
+            self.tokenizer.next_symbol_expect_eq(']', "enum field")?;
+        }
+
+        Ok(EnumValue {
+            name,
+            number,
+            options,
+        })
+    }
+
+    // enum = "enum" enumName enumBody
+    // enumBody = "{" { option | enumField | emptyStatement } "}"
+    fn next_enum_opt(&mut self) -> anyhow::Result<Option<WithLoc<Enumeration>>> {
+        let loc = self.tokenizer.lookahead_loc();
+
+        if self.tokenizer.next_ident_if_eq("enum")? {
+            let name = self.tokenizer.next_ident()?.to_owned();
+
+            let mut values = Vec::new();
+            let mut options = Vec::new();
+
+            self.tokenizer.next_symbol_expect_eq('{', "enum")?;
+            while self.tokenizer.lookahead_if_symbol()? != Some('}') {
+                // emptyStatement
+                if self.tokenizer.next_symbol_if_eq(';')? {
+                    continue;
+                }
+
+                if let Some(o) = self.next_option_opt()? {
+                    options.push(o);
+                    continue;
+                }
+
+                values.push(self.next_enum_field()?);
+            }
+            self.tokenizer.next_symbol_expect_eq('}', "enum")?;
+            let enumeration = Enumeration {
+                name,
+                values,
+                options,
+            };
+            Ok(Some(WithLoc {
+                loc,
+                t: enumeration,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Message definition
+
+    // messageBody = "{" { field | enum | message | extend | extensions | group |
+    //               option | oneof | mapField | reserved | emptyStatement } "}"
+    fn next_message_body(&mut self, mode: MessageBodyParseMode) -> anyhow::Result<MessageBody> {
+        self.tokenizer.next_symbol_expect_eq('{', "message body")?;
+
+        let mut r = MessageBody::default();
+
+        while self.tokenizer.lookahead_if_symbol()? != Some('}') {
+            let loc = self.tokenizer.lookahead_loc();
+
+            // emptyStatement
+            if self.tokenizer.next_symbol_if_eq(';')? {
+                continue;
+            }
+
+            if mode.is_most_non_fields_allowed() {
+                if let Some((field_nums, field_names)) = self.next_reserved_opt()? {
+                    r.reserved_nums.extend(field_nums);
+                    r.reserved_names.extend(field_names);
+                    continue;
+                }
+
+                if let Some(oneof) = self.next_oneof_opt()? {
+                    let one_of = FieldOrOneOf::OneOf(oneof);
+                    r.fields.push(WithLoc { t: one_of, loc });
+                    continue;
+                }
+
+                if let Some(extensions) = self.next_extend_opt()? {
+                    r.extensions.extend(extensions);
+                    continue;
+                }
+
+                if let Some(nested_message) = self.next_message_opt()? {
+                    r.messages.push(nested_message);
+                    continue;
+                }
+
+                if let Some(nested_enum) = self.next_enum_opt()? {
+                    r.enums.push(nested_enum);
+                    continue;
+                }
+            } else {
+                self.tokenizer.next_ident_if_eq_error("reserved")?;
+                self.tokenizer.next_ident_if_eq_error("oneof")?;
+                self.tokenizer.next_ident_if_eq_error("extend")?;
+                self.tokenizer.next_ident_if_eq_error("message")?;
+                self.tokenizer.next_ident_if_eq_error("enum")?;
+            }
+
+            if mode.is_extensions_allowed() {
+                if let Some(extension_ranges) = self.next_extensions_opt()? {
+                    r.extension_ranges.extend(extension_ranges);
+                    continue;
+                }
+            } else {
+                self.tokenizer.next_ident_if_eq_error("extensions")?;
+            }
+
+            if mode.is_option_allowed() {
+                if let Some(option) = self.next_option_opt()? {
+                    r.options.push(option);
+                    continue;
+                }
+            } else {
+                self.tokenizer.next_ident_if_eq_error("option")?;
+            }
+
+            let field = FieldOrOneOf::Field(self.next_field(mode)?);
+            r.fields.push(WithLoc { t: field, loc });
+        }
+
+        self.tokenizer.next_symbol_expect_eq('}', "message body")?;
+
+        Ok(r)
+    }
+
+    // message = "message" messageName messageBody
+    fn next_message_opt(&mut self) -> anyhow::Result<Option<WithLoc<Message>>> {
+        let loc = self.tokenizer.lookahead_loc();
+
+        if self.tokenizer.next_ident_if_eq("message")? {
+            let name = self.tokenizer.next_ident()?.to_owned();
+
+            let mode = match self.syntax {
+                Syntax::Proto2 => MessageBodyParseMode::MessageProto2,
+                Syntax::Proto3 => MessageBodyParseMode::MessageProto3,
+            };
+
+            let MessageBody {
+                fields,
+                reserved_nums,
+                reserved_names,
+                messages,
+                enums,
+                options,
+                extensions,
+                extension_ranges,
+            } = self.next_message_body(mode)?;
+
+            let message = Message {
+                name,
+                fields,
+                reserved_nums,
+                reserved_names,
+                messages,
+                enums,
+                options,
+                extensions,
+                extension_ranges,
+            };
+            Ok(Some(WithLoc { t: message, loc }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Extend
+
+    // extend = "extend" messageType "{" {field | group | emptyStatement} "}"
+    fn next_extend_opt(&mut self) -> anyhow::Result<Option<Vec<WithLoc<Extension>>>> {
+        let mut clone = self.clone();
+        if clone.tokenizer.next_ident_if_eq("extend")? {
+            // According to spec `extend` is only for `proto2`, but it is used in `proto3`
+            // https://github.com/google/protobuf/issues/4610
+
+            *self = clone;
+
+            let extendee = self.next_message_or_enum_type()?;
+
+            let mode = match self.syntax {
+                Syntax::Proto2 => MessageBodyParseMode::ExtendProto2,
+                Syntax::Proto3 => MessageBodyParseMode::ExtendProto3,
+            };
+
+            let MessageBody { fields, .. } = self.next_message_body(mode)?;
+
+            // TODO: is oneof allowed in extend?
+            let fields: Vec<WithLoc<Field>> = fields
+                .into_iter()
+                .map(|fo| match fo.t {
+                    FieldOrOneOf::Field(f) => Ok(f),
+                    FieldOrOneOf::OneOf(_) => Err(ParserError::OneOfInExtend),
+                })
+                .collect::<Result<_, ParserError>>()?;
+
+            let extensions = fields
+                .into_iter()
+                .map(|field| {
+                    let extendee = extendee.clone();
+                    let loc = field.loc;
+                    let extension = Extension { extendee, field };
+                    WithLoc { t: extension, loc }
+                })
+                .collect();
+
+            Ok(Some(extensions))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Service definition
+
+    fn next_options_or_colon(&mut self) -> anyhow::Result<Vec<ProtobufOption>> {
+        let mut options = Vec::new();
+        if self.tokenizer.next_symbol_if_eq('{')? {
+            while self.tokenizer.lookahead_if_symbol()? != Some('}') {
+                if let Some(option) = self.next_option_opt()? {
+                    options.push(option);
+                    continue;
+                }
+
+                if let Some(()) = self.next_empty_statement_opt()? {
+                    continue;
+                }
+
+                return Err(ParserError::IncorrectInput.into());
+            }
+            self.tokenizer.next_symbol_expect_eq('}', "option")?;
+        } else {
+            self.tokenizer.next_symbol_expect_eq(';', "option")?;
+        }
+
+        Ok(options)
+    }
+
+    // stream = "stream" streamName "(" messageType "," messageType ")"
+    //        (( "{" { option | emptyStatement } "}") | ";" )
+    fn next_stream_opt(&mut self) -> anyhow::Result<Option<Method>> {
+        assert_eq!(Syntax::Proto2, self.syntax);
+        if self.tokenizer.next_ident_if_eq("stream")? {
+            let name = self.tokenizer.next_ident()?;
+            self.tokenizer.next_symbol_expect_eq('(', "stream")?;
+            let input_type = self.next_message_or_enum_type()?;
+            self.tokenizer.next_symbol_expect_eq(',', "stream")?;
+            let output_type = self.next_message_or_enum_type()?;
+            self.tokenizer.next_symbol_expect_eq(')', "stream")?;
+            let options = self.next_options_or_colon()?;
+            Ok(Some(Method {
+                name,
+                input_type,
+                output_type,
+                client_streaming: true,
+                server_streaming: true,
+                options,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // rpc = "rpc" rpcName "(" [ "stream" ] messageType ")"
+    //     "returns" "(" [ "stream" ] messageType ")"
+    //     (( "{" { option | emptyStatement } "}" ) | ";" )
+    fn next_rpc_opt(&mut self) -> anyhow::Result<Option<Method>> {
+        if self.tokenizer.next_ident_if_eq("rpc")? {
+            let name = self.tokenizer.next_ident()?;
+            self.tokenizer.next_symbol_expect_eq('(', "rpc")?;
+            let client_streaming = self.tokenizer.next_ident_if_eq("stream")?;
+            let input_type = self.next_message_or_enum_type()?;
+            self.tokenizer.next_symbol_expect_eq(')', "rpc")?;
+            self.tokenizer.next_ident_expect_eq("returns")?;
+            self.tokenizer.next_symbol_expect_eq('(', "rpc")?;
+            let server_streaming = self.tokenizer.next_ident_if_eq("stream")?;
+            let output_type = self.next_message_or_enum_type()?;
+            self.tokenizer.next_symbol_expect_eq(')', "rpc")?;
+            let options = self.next_options_or_colon()?;
+            Ok(Some(Method {
+                name,
+                input_type,
+                output_type,
+                client_streaming,
+                server_streaming,
+                options,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // proto2:
+    // service = "service" serviceName "{" { option | rpc | stream | emptyStatement } "}"
+    //
+    // proto3:
+    // service = "service" serviceName "{" { option | rpc | emptyStatement } "}"
+    fn next_service_opt(&mut self) -> anyhow::Result<Option<WithLoc<Service>>> {
+        let loc = self.tokenizer.lookahead_loc();
+
+        if self.tokenizer.next_ident_if_eq("service")? {
+            let name = self.tokenizer.next_ident()?;
+            let mut methods = Vec::new();
+            let mut options = Vec::new();
+            self.tokenizer.next_symbol_expect_eq('{', "service")?;
+            while self.tokenizer.lookahead_if_symbol()? != Some('}') {
+                if let Some(method) = self.next_rpc_opt()? {
+                    methods.push(method);
+                    continue;
+                }
+
+                if self.syntax == Syntax::Proto2 {
+                    if let Some(method) = self.next_stream_opt()? {
+                        methods.push(method);
+                        continue;
+                    }
+                }
+
+                if let Some(o) = self.next_option_opt()? {
+                    options.push(o);
+                    continue;
+                }
+
+                if let Some(()) = self.next_empty_statement_opt()? {
+                    continue;
+                }
+
+                return Err(ParserError::IncorrectInput.into());
+            }
+            self.tokenizer.next_symbol_expect_eq('}', "service")?;
+            Ok(Some(WithLoc {
+                loc,
+                t: Service {
+                    name,
+                    methods,
+                    options,
+                },
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Proto file
+
+    // proto = syntax { import | package | option | topLevelDef | emptyStatement }
+    // topLevelDef = message | enum | extend | service
+    pub fn next_proto(&mut self) -> anyhow::Result<FileDescriptor> {
+        let syntax = self.next_syntax()?.unwrap_or(Syntax::Proto2);
+        self.syntax = syntax;
+
+        let mut imports = Vec::new();
+        let mut package = ProtobufAbsPath::root();
+        let mut messages = Vec::new();
+        let mut enums = Vec::new();
+        let mut extensions = Vec::new();
+        let mut options = Vec::new();
+        let mut services = Vec::new();
+
+        while !self.tokenizer.syntax_eof()? {
+            if let Some(import) = self.next_import_opt()? {
+                imports.push(import);
+                continue;
+            }
+
+            if let Some(next_package) = self.next_package_opt()? {
+                package = next_package;
+                continue;
+            }
+
+            if let Some(option) = self.next_option_opt()? {
+                options.push(option);
+                continue;
+            }
+
+            if let Some(message) = self.next_message_opt()? {
+                messages.push(message);
+                continue;
+            }
+
+            if let Some(enumeration) = self.next_enum_opt()? {
+                enums.push(enumeration);
+                continue;
+            }
+
+            if let Some(more_extensions) = self.next_extend_opt()? {
+                extensions.extend(more_extensions);
+                continue;
+            }
+
+            if let Some(service) = self.next_service_opt()? {
+                services.push(service);
+                continue;
+            }
+
+            if self.tokenizer.next_symbol_if_eq(';')? {
+                continue;
+            }
+
+            return Err(ParserError::IncorrectInput.into());
+        }
+
+        Ok(FileDescriptor {
+            imports,
+            package,
+            syntax,
+            messages,
+            enums,
+            extensions,
+            services,
+            options,
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn parse<P, R>(input: &str, parse_what: P) -> R
+    where
+        P: FnOnce(&mut Parser) -> anyhow::Result<R>,
+    {
+        let mut parser = Parser::new(input);
+        let r =
+            parse_what(&mut parser).expect(&format!("parse failed at {}", parser.tokenizer.loc()));
+        let eof = parser
+            .tokenizer
+            .syntax_eof()
+            .expect(&format!("check eof failed at {}", parser.tokenizer.loc()));
+        assert!(eof, "{}", parser.tokenizer.loc());
+        r
+    }
+
+    fn parse_opt<P, R>(input: &str, parse_what: P) -> R
+    where
+        P: FnOnce(&mut Parser) -> anyhow::Result<Option<R>>,
+    {
+        let mut parser = Parser::new(input);
+        let o =
+            parse_what(&mut parser).expect(&format!("parse failed at {}", parser.tokenizer.loc()));
+        let r = o.expect(&format!(
+            "parser returned none at {}",
+            parser.tokenizer.loc()
+        ));
+        assert!(parser.tokenizer.syntax_eof().unwrap());
+        r
+    }
+
+    #[test]
+    fn test_syntax() {
+        let msg = r#"  syntax = "proto3";  "#;
+        let mess = parse_opt(msg, |p| p.next_syntax());
+        assert_eq!(Syntax::Proto3, mess);
+    }
+
+    #[test]
+    fn test_field_default_value_int() {
+        let msg = r#"  optional int64 f = 4 [default = 12];  "#;
+        let mess = parse(msg, |p| p.next_field(MessageBodyParseMode::MessageProto2));
+        assert_eq!("f", mess.t.name);
+        assert_eq!(
+            ProtobufOptionName::simple("default"),
+            mess.t.options[0].name
+        );
+        assert_eq!("12", mess.t.options[0].value.format());
+    }
+
+    #[test]
+    fn test_field_default_value_float() {
+        let msg = r#"  optional float f = 2 [default = 10.0];  "#;
+        let mess = parse(msg, |p| p.next_field(MessageBodyParseMode::MessageProto2));
+        assert_eq!("f", mess.t.name);
+        assert_eq!(
+            ProtobufOptionName::simple("default"),
+            mess.t.options[0].name
+        );
+        assert_eq!("10", mess.t.options[0].value.format());
+    }
+
+    #[test]
+    fn test_message() {
+        let msg = r#"message ReferenceData
+    {
+        repeated ScenarioInfo  scenarioSet = 1;
+        repeated CalculatedObjectInfo calculatedObjectSet = 2;
+        repeated RiskFactorList riskFactorListSet = 3;
+        repeated RiskMaturityInfo riskMaturitySet = 4;
+        repeated IndicatorInfo indicatorSet = 5;
+        repeated RiskStrikeInfo riskStrikeSet = 6;
+        repeated FreeProjectionList freeProjectionListSet = 7;
+        repeated ValidationProperty ValidationSet = 8;
+        repeated CalcProperties calcPropertiesSet = 9;
+        repeated MaturityInfo maturitySet = 10;
+    }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(10, mess.t.fields.len());
+    }
+
+    #[test]
+    fn test_enum() {
+        let msg = r#"enum PairingStatus {
+                DEALPAIRED        = 0;
+                INVENTORYORPHAN   = 1;
+                CALCULATEDORPHAN  = 2;
+                CANCELED          = 3;
+    }"#;
+
+        let enumeration = parse_opt(msg, |p| p.next_enum_opt());
+        assert_eq!(4, enumeration.values.len());
+    }
+
+    #[test]
+    fn test_ignore() {
+        let msg = r#"option optimize_for = SPEED;"#;
+
+        parse_opt(msg, |p| p.next_option_opt());
+    }
+
+    #[test]
+    fn test_import() {
+        let msg = r#"syntax = "proto3";
+
+    import "test_import_nested_imported_pb.proto";
+
+    message ContainsImportedNested {
+        ContainerForNested.NestedMessage m = 1;
+        ContainerForNested.NestedEnum e = 2;
+    }
+    "#;
+        let desc = parse(msg, |p| p.next_proto());
+
+        assert_eq!(
+            vec!["test_import_nested_imported_pb.proto"],
+            desc.imports
+                .into_iter()
+                .map(|i| i.path.to_str().to_owned())
+                .collect::<Vec<_>>()
+        );
+    }
+
+    #[test]
+    fn test_nested_message() {
+        let msg = r#"message A
+    {
+        message B {
+            repeated int32 a = 1;
+            optional string b = 2;
+        }
+        optional string b = 1;
+    }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(1, mess.t.messages.len());
+    }
+
+    #[test]
+    fn test_map() {
+        let msg = r#"message A
+    {
+        optional map<string, int32> b = 1;
+    }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(1, mess.t.fields.len());
+        match mess.t.regular_fields_for_test()[0].typ {
+            FieldType::Map(ref f) => match &**f {
+                &(FieldType::String, FieldType::Int32) => (),
+                ref f => panic!("Expecting Map<String, Int32> found {:?}", f),
+            },
+            ref f => panic!("Expecting map, got {:?}", f),
+        }
+    }
+
+    #[test]
+    fn test_oneof() {
+        let msg = r#"message A
+    {
+        optional int32 a1 = 1;
+        oneof a_oneof {
+            string a2 = 2;
+            int32 a3 = 3;
+            bytes a4 = 4;
+        }
+        repeated bool a5 = 5;
+    }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(1, mess.t.oneofs().len());
+        assert_eq!(3, mess.t.oneofs()[0].fields.len());
+    }
+
+    #[test]
+    fn test_reserved() {
+        let msg = r#"message Sample {
+       reserved 4, 15, 17 to 20, 30;
+       reserved "foo", "bar";
+       optional uint64 age =1;
+       required bytes name =2;
+    }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(
+            vec![
+                FieldNumberRange { from: 4, to: 4 },
+                FieldNumberRange { from: 15, to: 15 },
+                FieldNumberRange { from: 17, to: 20 },
+                FieldNumberRange { from: 30, to: 30 }
+            ],
+            mess.t.reserved_nums
+        );
+        assert_eq!(
+            vec!["foo".to_string(), "bar".to_string()],
+            mess.t.reserved_names
+        );
+        assert_eq!(2, mess.t.fields.len());
+    }
+
+    #[test]
+    fn test_default_value_int() {
+        let msg = r#"message Sample {
+            optional int32 x = 1 [default = 17];
+        }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(
+            ProtobufOptionName::simple("default"),
+            mess.t.regular_fields_for_test()[0].options[0].name
+        );
+        assert_eq!(
+            "17",
+            mess.t.regular_fields_for_test()[0].options[0]
+                .value
+                .format()
+        );
+    }
+
+    #[test]
+    fn test_default_value_string() {
+        let msg = r#"message Sample {
+            optional string x = 1 [default = "ab\nc d\"g\'h\0\"z"];
+        }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(
+            r#""ab\nc d\"g\'h\0\"z""#,
+            mess.t.regular_fields_for_test()[0].options[0]
+                .value
+                .format()
+        );
+    }
+
+    #[test]
+    fn test_default_value_bytes() {
+        let msg = r#"message Sample {
+            optional bytes x = 1 [default = "ab\nc d\xfeE\"g\'h\0\"z"];
+        }"#;
+
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+        assert_eq!(
+            r#""ab\nc d\xfeE\"g\'h\0\"z""#,
+            mess.t.regular_fields_for_test()[0].options[0]
+                .value
+                .format()
+        );
+    }
+
+    #[test]
+    fn test_group() {
+        let msg = r#"message MessageWithGroup {
+            optional string aaa = 1;
+
+            repeated group Identifier = 18 {
+                optional int32 iii = 19;
+                optional string sss = 20;
+            }
+
+            required int bbb = 3;
+        }"#;
+        let mess = parse_opt(msg, |p| p.next_message_opt());
+
+        assert_eq!("identifier", mess.t.regular_fields_for_test()[1].name);
+        if let FieldType::Group(Group { fields, .. }) = &mess.t.regular_fields_for_test()[1].typ {
+            assert_eq!(2, fields.len());
+        } else {
+            panic!("expecting group");
+        }
+
+        assert_eq!("bbb", mess.t.regular_fields_for_test()[2].name);
+    }
+
+    #[test]
+    fn test_incorrect_file_descriptor() {
+        let msg = r#"
+            message Foo {}
+
+            dfgdg
+        "#;
+
+        let err = FileDescriptor::parse(msg).err().expect("err");
+        assert_eq!(4, err.line);
+    }
+}
diff --git a/src/rel_path.rs b/src/rel_path.rs
new file mode 100644
index 0000000..a6e61e3
--- /dev/null
+++ b/src/rel_path.rs
@@ -0,0 +1,59 @@
+use std::ops::Deref;
+use std::path::Path;
+use std::path::PathBuf;
+
+/// Wrapper for `Path` that asserts that the path is relative.
+#[repr(transparent)]
+pub(crate) struct RelPath {
+    path: Path,
+}
+
+/// Wrapper for `PathBuf` that asserts that the path is relative.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub(crate) struct RelPathBuf {
+    path: PathBuf,
+}
+
+impl RelPath {
+    pub(crate) fn new(path: &Path) -> &RelPath {
+        assert!(
+            !path.is_absolute(),
+            "path must be relative: {}",
+            path.display()
+        );
+        unsafe { &*(path as *const Path as *const RelPath) }
+    }
+
+    pub(crate) fn _to_owned(&self) -> RelPathBuf {
+        RelPathBuf {
+            path: self.path.to_owned(),
+        }
+    }
+}
+
+impl RelPathBuf {
+    pub(crate) fn _new(path: PathBuf) -> RelPathBuf {
+        assert!(
+            !path.is_absolute(),
+            "path must be relative: {}",
+            path.display()
+        );
+        RelPathBuf { path }
+    }
+}
+
+impl Deref for RelPath {
+    type Target = Path;
+
+    fn deref(&self) -> &Self::Target {
+        &self.path
+    }
+}
+
+impl Deref for RelPathBuf {
+    type Target = RelPath;
+
+    fn deref(&self) -> &Self::Target {
+        RelPath::new(&self.path)
+    }
+}
diff --git a/src/test_against_protobuf_protos.rs b/src/test_against_protobuf_protos.rs
new file mode 100644
index 0000000..5c1707a
--- /dev/null
+++ b/src/test_against_protobuf_protos.rs
@@ -0,0 +1,40 @@
+#![cfg(test)]
+
+use std::fs;
+use std::io::Read;
+use std::path::Path;
+
+use anyhow::Context;
+
+use crate::model;
+
+fn parse_recursively(path: &Path) {
+    assert!(path.exists());
+
+    let file_name = path
+        .file_name()
+        .expect("file_name")
+        .to_str()
+        .expect("to_str");
+    if path.is_dir() {
+        for entry in fs::read_dir(path).expect("read_dir") {
+            parse_recursively(&entry.expect("entry").path());
+        }
+    } else if file_name.ends_with(".proto") {
+        println!("checking {}", path.display());
+        let mut content = String::new();
+        fs::File::open(path)
+            .expect("open")
+            .read_to_string(&mut content)
+            .expect("read");
+        model::FileDescriptor::parse(&content)
+            .with_context(|| format!("testing `{}`", path.display()))
+            .expect("parse");
+    }
+}
+
+#[test]
+fn test() {
+    let path = &Path::new("../google-protobuf-all-protos/protobuf");
+    parse_recursively(&Path::new(path));
+}
diff --git a/src/which_parser.rs b/src/which_parser.rs
new file mode 100644
index 0000000..223da0d
--- /dev/null
+++ b/src/which_parser.rs
@@ -0,0 +1,14 @@
+/// Which parse to use to parse `.proto` files.
+#[derive(Debug, Copy, Clone)]
+pub(crate) enum WhichParser {
+    /// Pure Rust parser implemented by this crate.
+    Pure,
+    /// Parse `.proto` files using `protoc --descriptor_set_out=...` command.
+    Protoc,
+}
+
+impl Default for WhichParser {
+    fn default() -> Self {
+        WhichParser::Pure
+    }
+}
diff --git a/tests/bundled_proto_consistent.rs b/tests/bundled_proto_consistent.rs
new file mode 100644
index 0000000..ef20ef9
--- /dev/null
+++ b/tests/bundled_proto_consistent.rs
@@ -0,0 +1,56 @@
+use std::fs;
+use std::path::Path;
+use std::path::PathBuf;
+
+fn list_dir(p: &Path) -> Vec<PathBuf> {
+    let mut children = fs::read_dir(p)
+        .unwrap()
+        .map(|r| r.map(|e| e.path()))
+        .collect::<Result<Vec<_>, _>>()
+        .unwrap();
+    children.sort();
+    children
+}
+
+fn assert_equal_recursively(a: &Path, b: &Path) {
+    assert_eq!(a.is_dir(), b.is_dir(), "{} {}", a.display(), b.display());
+    assert_eq!(a.is_file(), b.is_file(), "{} {}", a.display(), b.display());
+    if a.is_dir() {
+        let mut a_contents = list_dir(a).into_iter();
+        let mut b_contents = list_dir(b).into_iter();
+        loop {
+            let a_child = a_contents.next();
+            let b_child = b_contents.next();
+            match (a_child, b_child) {
+                (Some(a_child), Some(b_child)) => {
+                    assert_eq!(a_child.file_name(), b_child.file_name());
+                    assert_equal_recursively(&a_child, &b_child);
+                }
+                (None, None) => break,
+                _ => panic!(
+                    "mismatched directories: {} and {}",
+                    a.display(),
+                    b.display()
+                ),
+            }
+        }
+    } else {
+        let a_contents = fs::read_to_string(a).unwrap();
+        let b_contents = fs::read_to_string(b).unwrap();
+        assert_eq!(a_contents, b_contents);
+    }
+}
+
+#[test]
+fn test_bundled_google_proto_files_consistent() {
+    let source = "../proto/google";
+    let our_copy = "src/proto/google";
+    assert_equal_recursively(Path::new(source), Path::new(our_copy));
+}
+
+#[test]
+fn test_bundled_rustproto_proto_consistent() {
+    let source = "../proto/rustproto.proto";
+    let our_copy = "src/proto/rustproto.proto";
+    assert_equal_recursively(Path::new(source), Path::new(our_copy));
+}