Merge remote-tracking branch 'origin/upstream'
Import b/312432424
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..2b9eb0f
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "1f39fbe09ef7d840452691930d419c743ded3b8f"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58f4d26
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+**/target
+Cargo.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4846e10
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,102 @@
+# Change Log
+
+## [Unreleased][unreleased]
+
+### Changed/Fixed
+
+### Added
+
+### Thanks
+
+## 0.5.2
+
+### Changed/Fixed
+
+- Fix decoding of integers: check if value will wrap if integer is signed
+- Fix encoding of integers (add 0x00 prefix when required, and remove extra 0xff for negative integers)
+- Fix a small math error in GeneralizedTime
+- Introduce trait GetObjectContent, use `from_ber` when skipping BER content (closes #14)
+
+### Thanks
+
+- Nadja Reitzenstein, Christian Speich
+
+## 0.5.1
+
+Minor fixes:
+
+- Fix constraints too strict on `TaggedValue::FromDer`, do not auto-derive
+- Update oid-registry
+- Fix `Any::as_relative_oid` to take a reference (and not consume input)
+
+derive:
+
+- Add special case handler for alias to Any
+- Add support for DEFAULT attribute
+
+## 0.5.0
+
+This release adds some new methods and custom derive attributes.
+It also adds a lot of tests to improve code coverage.
+
+asn1-rs:
+
+- Add helper types for Application/Private tagged values
+- Any: add methods `from_ber_and_then` (and `_der`)
+- TaggedParser: add documentation for `from_ber_and_then` (and `_der`)
+- Oid: add method `starts_with`
+- Fix documentation of application and private tagged helpers
+- Fix clippy warnings
+
+derive:
+
+- Add custom derive BerAlias and DerAlias
+
+coverage:
+
+- Add many tests to improve coverage
+
+## 0.4.2
+
+Bugfix release:
+- Remove explicit output lifetime in traits
+- Fix wrong encoding `BmpString` when using `ToDer`
+- Fix parsing of some EmbeddedPdv subtypes
+- Fix encoded length for Enumerated
+- Add missing `DerAutoDerive` impl for bool
+- Add missing `DerAutoDerive` impl for f32/f64
+- Remove redundant check, `Any::from_der` checks than length is definite
+- Length: fix potential bug when adding Length + Indefinite
+- Fix inverted logic in `Header::assert_definite()`
+
+## 0.4.1
+
+Minor fix:
+- add missing file in distribution (fix docs.rs build)
+
+## 0.4.0
+
+asn1-rs:
+
+- Add generic error parameter in traits and in types
+  - This was added for all types except a few (like `Vec<T>` or `BTreeSet<T>`) due to
+    Rust compiler limitations
+- Add `DerAutoDerive` trait to control manual/automatic implementation of `FromDer`
+  - This allow controlling automatic trait implementation, and providing manual
+    implementations of both `FromDer` and `CheckDerConstraints`
+- UtcTime: Introduce utc_adjusted_date() to map 2 chars years date to 20/21 centuries date (#9)
+
+derive:
+
+- Add attributes to simplify deriving EXPLICIT, IMPLICIT and OPTIONAL
+- Add support for different tag classes (like APPLICATION or PRIVATE)
+- Add support for custom errors and mapping errors
+- Add support for deriving BER/DER SET
+- DerDerive: derive both CheckDerConstraints and FromDer
+
+documentation:
+
+- Add doc modules for recipes and for custom derive attributes
+- Add note on trailing bytes being ignored in sequence
+- Improve documentation for notation with braces in TaggedValue
+- Improve documentation
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..57a55b8
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,127 @@
+# 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 = "2018"
+rust-version = "1.53"
+name = "asn1-rs"
+version = "0.5.2"
+authors = ["Pierre Chifflier <chifflier@wzdftpd.net>"]
+include = [
+    "CHANGELOG.md",
+    "LICENSE-*",
+    "README.md",
+    ".gitignore",
+    "Cargo.toml",
+    "doc/*.md",
+    "examples/*.rs",
+    "src/*.rs",
+    "src/asn1_types/*.rs",
+    "src/asn1_types/real/*.rs",
+    "src/asn1_types/sequence/*.rs",
+    "src/asn1_types/set/*.rs",
+    "src/asn1_types/strings/*.rs",
+    "src/asn1_types/tagged/*.rs",
+    "src/ber/*.rs",
+    "src/doc/*.rs",
+    "tests/*.rs",
+]
+description = "Parser/encoder for ASN.1 BER/DER data"
+homepage = "https://github.com/rusticata/asn1-rs"
+readme = "README.md"
+keywords = [
+    "BER",
+    "DER",
+    "ASN1",
+    "parser",
+    "nom",
+]
+categories = ["parser-implementations"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/rusticata/asn1-rs.git"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+    "--cfg",
+    "docsrs",
+]
+
+[dependencies.asn1-rs-derive]
+version = "0.4"
+
+[dependencies.asn1-rs-impl]
+version = "0.1"
+
+[dependencies.bitvec]
+version = "1.0"
+optional = true
+
+[dependencies.cookie-factory]
+version = "0.3.0"
+optional = true
+
+[dependencies.displaydoc]
+version = "0.2.2"
+
+[dependencies.nom]
+version = "7.0"
+features = ["std"]
+default_features = false
+
+[dependencies.num-bigint]
+version = "0.4"
+optional = true
+
+[dependencies.num-traits]
+version = "0.2.14"
+
+[dependencies.rusticata-macros]
+version = "4.0"
+
+[dependencies.thiserror]
+version = "1.0.25"
+
+[dependencies.time]
+version = "0.3"
+features = [
+    "macros",
+    "parsing",
+    "formatting",
+]
+optional = true
+
+[dev-dependencies.colored]
+version = "2.0"
+
+[dev-dependencies.hex-literal]
+version = "0.3.1"
+
+[dev-dependencies.oid-registry]
+version = "0.6"
+features = [
+    "crypto",
+    "x509",
+]
+
+[dev-dependencies.pem]
+version = "1.0"
+
+[dev-dependencies.trybuild]
+version = "1.0"
+
+[features]
+bigint = ["num-bigint"]
+bits = ["bitvec"]
+datetime = ["time"]
+default = ["std"]
+serialize = ["cookie-factory"]
+std = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..2775ae6
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,66 @@
+[package]
+description = "Parser/encoder for ASN.1 BER/DER data"
+license = "MIT/Apache-2.0"
+keywords = ["BER","DER","ASN1","parser","nom"]
+homepage = "https://github.com/rusticata/asn1-rs"
+repository = "https://github.com/rusticata/asn1-rs.git"
+name = "asn1-rs"
+version = "0.5.2"
+authors = ["Pierre Chifflier <chifflier@wzdftpd.net>"]
+categories = ["parser-implementations"]
+readme = "README.md"
+edition = "2018"
+rust-version = "1.53"
+
+include = [
+  "CHANGELOG.md",
+  "LICENSE-*",
+  "README.md",
+  ".gitignore",
+  "Cargo.toml",
+  "doc/*.md",
+  "examples/*.rs",
+  "src/*.rs",
+  "src/asn1_types/*.rs",
+  "src/asn1_types/real/*.rs",
+  "src/asn1_types/sequence/*.rs",
+  "src/asn1_types/set/*.rs",
+  "src/asn1_types/strings/*.rs",
+  "src/asn1_types/tagged/*.rs",
+  "src/ber/*.rs",
+  "src/doc/*.rs",
+  "tests/*.rs",
+]
+
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[features]
+default = ["std"]
+bigint = ["num-bigint"]
+bits = ["bitvec"]
+datetime = ["time"]
+serialize = ["cookie-factory"]
+std = []
+
+[dependencies]
+asn1-rs-derive = { version="0.4", path="./derive" }
+asn1-rs-impl = { version="0.1", path="./impl" }
+bitvec = { version="1.0", optional=true }
+cookie-factory = { version="0.3.0", optional=true }
+displaydoc = "0.2.2"
+nom = { version="7.0", default_features=false, features=["std"] }
+num-bigint = { version = "0.4", optional = true }
+num-traits = "0.2.14"
+rusticata-macros = "4.0"
+thiserror = "1.0.25"
+time = { version="0.3", features=["macros", "parsing", "formatting"], optional=true }
+
+[dev-dependencies]
+colored = "2.0"
+hex-literal = "0.3.1"
+oid-registry = { version="0.6", features=["crypto","x509"] }
+pem = "1.0"
+trybuild = "1.0"
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE
\ No newline at end of file
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..290e7b9
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2017 Pierre Chifflier
+
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..b892fa6
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "asn1-rs"
+description: "Parser/encoder for ASN.1 BER/DER data"
+third_party {
+  identifier {
+    type: "crates.io"
+    value: "https://crates.io/crates/asn1-rs"
+  }
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/asn1-rs/asn1-rs-0.5.2.crate"
+  }
+  version: "0.5.2"
+  # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 11
+    day: 21
+  }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..5a2b844
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80fa4f6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,179 @@
+<!-- cargo-sync-readme start -->
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT)
+[![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE)
+[![docs.rs](https://docs.rs/asn1-rs/badge.svg)](https://docs.rs/asn1-rs)
+[![crates.io](https://img.shields.io/crates/v/asn1-rs.svg)](https://crates.io/crates/asn1-rs)
+[![Download numbers](https://img.shields.io/crates/d/asn1-rs.svg)](https://crates.io/crates/asn1-rs)
+[![Github CI](https://github.com/rusticata/asn1-rs/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/asn1-rs/actions)
+[![Minimum rustc version](https://img.shields.io/badge/rustc-1.53.0+-lightgray.svg)](#rust-version-requirements)
+
+# BER/DER Parsers/Encoders
+
+A set of parsers/encoders for Basic Encoding Rules (BER [[X.690]]) and Distinguished Encoding Rules(DER
+[[X.690]]) formats, implemented with the [nom] parser combinator framework.
+
+It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken
+to ensure security and safety of this crate, including design (recursion limit, defensive
+programming), tests, and fuzzing. It also aims to be panic-free.
+
+This crate is a rewrite of [der-parser](https://crates.io/crates/der-parser) to propose a more data-oriented API,
+and add generalized support for serialization.
+
+Many ideas were borrowed from the [crypto/utils/der](https://github.com/RustCrypto/utils/tree/master/der) crate (like
+the `Any`/`TryFrom`/`FromDer` mechanism), adapted and merged into a generalized BER/DER crate.
+Credits (and many thanks) go to Tony Arcieri for writing the original crate.
+
+# BER/DER parsers
+
+BER stands for Basic Encoding Rules, and is defined in [[X.690]]. It defines a set of rules to
+encode and decode ASN.1 [[X.680]] objects in binary.
+
+[[X.690]] also defines Distinguished Encoding Rules (DER), which is BER with added rules to
+ensure canonical and unequivocal binary representation of objects.
+
+The choice of which one to use is usually guided by the speficication of the data format based
+on BER or DER: for example, X.509 uses DER as encoding representation.
+
+The main traits for parsing are the [`FromBer`] and [`FromDer`] traits.
+These traits provide methods to parse binary input, and return either the remaining (unparsed) bytes
+and the parsed object, or an error.
+
+The parsers follow the interface from [nom], and the [`ParseResult`] object is a specialized version
+of `nom::IResult`. This means that most `nom` combinators (`map`, `many0`, etc.) can be used in
+combination to objects and methods from this crate. Reading the nom documentation may
+help understanding how to write and combine parsers and use the output.
+
+**Minimum Supported Rust Version**: 1.53.0
+
+Note: if the `bits` feature is enabled, MSRV is 1.56.0 (due to `bitvec` 1.0)
+
+# Recipes
+
+See [doc::recipes] and [doc::derive] for more examples and recipes.
+
+## Examples
+
+Parse 2 BER integers:
+
+```rust
+use asn1_rs::{Integer, FromBer};
+
+let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
+              0x02, 0x03, 0x01, 0x00, 0x00,
+];
+
+let (rem, obj1) = Integer::from_ber(&bytes).expect("parsing failed");
+let (rem, obj2) = Integer::from_ber(&bytes).expect("parsing failed");
+
+assert_eq!(obj1, Integer::from_u32(65537));
+```
+
+In the above example, the generic [`Integer`] type is used. This type can contain integers of any
+size, but do not provide a simple API to manipulate the numbers.
+
+In most cases, the integer either has a limit, or is expected to fit into a primitive type.
+To get a simple value, just use the `from_ber`/`from_der` methods on the primitive types:
+
+```rust
+use asn1_rs::FromBer;
+
+let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
+              0x02, 0x03, 0x01, 0x00, 0x00,
+];
+
+let (rem, obj1) = u32::from_ber(&bytes).expect("parsing failed");
+let (rem, obj2) = u32::from_ber(&rem).expect("parsing failed");
+
+assert_eq!(obj1, 65537);
+assert_eq!(obj2, 65536);
+```
+
+If the parsing succeeds, but the integer cannot fit into the expected type, the method will return
+an `IntegerTooLarge` error.
+
+# BER/DER encoders
+
+BER/DER encoding is symmetrical to decoding, using the traits `ToBer` and [`ToDer`] traits.
+These traits provide methods to write encoded content to objects with the `io::Write` trait,
+or return an allocated `Vec<u8>` with the encoded data.
+If the serialization fails, an error is returned.
+
+## Examples
+
+Writing 2 BER integers:
+
+```rust
+use asn1_rs::{Integer, ToDer};
+
+let mut writer = Vec::new();
+
+let obj1 = Integer::from_u32(65537);
+let obj2 = Integer::from_u32(65536);
+
+let _ = obj1.write_der(&mut writer).expect("serialization failed");
+let _ = obj2.write_der(&mut writer).expect("serialization failed");
+
+let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
+               0x02, 0x03, 0x01, 0x00, 0x00,
+];
+assert_eq!(&writer, bytes);
+```
+
+Similarly to `FromBer`/`FromDer`, serialization methods are also implemented for primitive types:
+
+```rust
+use asn1_rs::ToDer;
+
+let mut writer = Vec::new();
+
+let _ = 65537.write_der(&mut writer).expect("serialization failed");
+let _ = 65536.write_der(&mut writer).expect("serialization failed");
+
+let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
+               0x02, 0x03, 0x01, 0x00, 0x00,
+];
+assert_eq!(&writer, bytes);
+```
+
+If the parsing succeeds, but the integer cannot fit into the expected type, the method will return
+an `IntegerTooLarge` error.
+
+## Changes
+
+See `CHANGELOG.md`.
+
+# References
+
+- [[X.680]] Abstract Syntax Notation One (ASN.1): Specification of basic notation.
+- [[X.690]] ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical
+  Encoding Rules (CER) and Distinguished Encoding Rules (DER).
+
+[X.680]: http://www.itu.int/rec/T-REC-X.680/en "Abstract Syntax Notation One (ASN.1):
+  Specification of basic notation."
+[X.690]: https://www.itu.int/rec/T-REC-X.690/en "ASN.1 encoding rules: Specification of
+  Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules
+  (DER)."
+[nom]: https://github.com/Geal/nom "Nom parser combinator framework"
+<!-- cargo-sync-readme end -->
+
+## Changes
+
+See `CHANGELOG.md`, and `UPGRADING.md` for instructions for upgrading major versions.
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0
+   ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license
+   ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+## Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/doc/DERIVE.md b/doc/DERIVE.md
new file mode 100644
index 0000000..5322140
--- /dev/null
+++ b/doc/DERIVE.md
@@ -0,0 +1,323 @@
+# BER/DER Custom Derive Attributes
+
+## BER/DER Sequence parsers
+
+### `BER`
+
+To derive a BER `SEQUENCE` parser, add the [`BerSequence`] derive attribute to an existing struct. Parsers will be derived automatically for all fields, which must implement the [`FromBer`] trait.
+
+For ex:
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, BerSequence)]
+pub struct S {
+    a: u32,
+    b: u16,
+    c: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rest, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+After parsing b, any bytes that were leftover and not used to fill val will be returned in `rest`.
+
+When parsing a `SEQUENCE` into a struct, any trailing elements of the `SEQUENCE` that do
+not have matching fields in val will not be included in `rest`, as these are considered
+valid elements of the `SEQUENCE` and not trailing data.
+
+### `DER`
+
+To derive a `DER` parser, use the [`DerSequence`] custom attribute.
+
+*Note: the `DerSequence` attributes derive both `BER` and `DER` parsers.*
+
+## Tagged values
+
+### `EXPLICIT`
+
+There are several ways of parsing tagged values: either using types like [`TaggedExplicit`], or using custom annotations.
+
+Using `TaggedExplicit` works as usual. The only drawback is that the type is visible in the structure, so accessing the value must be done using `.as_ref()` or `.into_inner()`:
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S2 {
+    a: u16,
+}
+
+// test with EXPLICIT Vec
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S {
+    // a INTEGER
+    a: u32,
+    // b INTEGER
+    b: u16,
+    // c [0] EXPLICIT SEQUENCE OF S2
+    c: TaggedExplicit<Vec<S2>, Error, 0>,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+
+// Get a reference on c (type is &Vec<S2>)
+let ref_c = result.c.as_ref();
+# Ok(()) };
+```
+
+*Note: tags are context-specific by default. To specify other kind of tags (like `APPLICATION`) use [`TaggedValue`].*
+
+### `tag_explicit`
+
+To "hide" the tag from the parser, the `tag_explicit` attribute is provided. This attribute must specify the tag value (as an integer), and will automatically wrap reading the value with the specified tag.
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S {
+    // a [0] EXPLICIT INTEGER
+    #[tag_explicit(0)]
+    a: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+This method handles transparently the encapsulation and the read of the tagged value.
+
+*Note: tags are context-specific by default. To specify other kind of tags (like `APPLICATION`) add the tag class before the value in the `tag_explicit` attribute.*
+For ex: `tag_explicit(APPLICATION 0)` or `tag_explicit(PRIVATE 2)`.
+
+### Tagged optional values
+
+The `optional` custom attribute can be used in addition of `tag_explicit` to specify that the value is `OPTIONAL`.
+
+The type of the annotated field member must be resolvable to `Option`.
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S {
+    // a [0] EXPLICIT INTEGER OPTIONAL
+    #[tag_explicit(0)]
+    #[optional]
+    a: Option<u16>,
+    // b INTEGER
+    b: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+### `IMPLICIT`
+
+Tagged `IMPLICIT` values are handled similarly as for `EXPLICIT`, and can be parsed either using the [`TaggedImplicit`] type, or using the `tag_implicit` custom attribute.
+
+For ex:
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S {
+    // a [0] IMPLICIT INTEGER OPTIONAL
+    #[tag_implicit(0)]
+    #[optional]
+    a: Option<u16>,
+    // b INTEGER
+    b: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+## `OPTIONAL` values (not tagged)
+
+The `optional` custom attribute can be specified to indicate the value is `OPTIONAL`.
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+pub struct S {
+    // a INTEGER
+    a: u16,
+    // b INTEGER OPTIONAL
+    #[optional]
+    b: Option<u16>,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+**Important**: there are several limitations to this attribute.
+
+In particular, the parser is eager: when an `OPTIONAL` value of some type is followed by another value (not `OPTIONAL`) of the same type, this can create problem.
+If only one value is present, the parser will affect it to the first field, and then raise an error because the second is absent.
+
+Note that this does not concern tagged optional values (unless they have the same tag).
+
+## `DEFAULT`
+
+The `default` custom attribute can be specified to indicate the value has a `DEFAULT` attribute. The value can also be marked as
+`OPTIONAL`, but this can be omitted.
+
+Since the value can always be obtained, the type should not be `Option<T>`, but should use `T` directly.
+
+```rust
+# use asn1_rs::*;
+#[derive(Debug, PartialEq, DerSequence)]
+#[debug_derive]
+pub struct S {
+    // a INTEGER
+    a: u16,
+    // b INTEGER DEFAULT 0
+    #[default(0_u16)]
+    b: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+# Ok(()) };
+```
+
+Limitations are the same as for `OPTIONAL` attribute.
+
+## Debugging
+
+To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+
+When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+
+Example:
+```rust
+use asn1_rs::*;
+
+#[derive(BerSequence)]
+#[debug_derive]
+struct S {
+  a: u32,
+}
+```
+
+## BER/DER Set parsers
+
+Parsing BER/DER `SET` objects is very similar to `SEQUENCE`. Use the [`BerSet`] and [`DerSet`] custom derive attributes on the structure, and everything else is exactly the same as for sequences (see above for documentation).
+
+Example:
+```rust
+# use asn1_rs::*;
+use std::collections::BTreeSet;
+
+// `Ord` is needed because we will parse as a `BTreeSet` later
+#[derive(Debug, DerSet, PartialEq, Eq, PartialOrd, Ord)]
+pub struct S2 {
+    a: u16,
+}
+
+// test with EXPLICIT Vec
+#[derive(Debug, PartialEq, DerSet)]
+pub struct S {
+    // a INTEGER
+    a: u32,
+    // b INTEGER
+    b: u16,
+    // c [0] EXPLICIT SET OF S2
+    c: TaggedExplicit<BTreeSet<S2>, Error, 0>,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_ber(input)?;
+
+// Get a reference on c (type is &BTreeSet<S2>)
+let ref_c = result.c.as_ref();
+# Ok(()) };
+```
+
+# Advanced
+
+## Custom errors
+
+Derived parsers can use the `error` attribute to specify the error type of the parser.
+
+The custom error type must implement `From<Error>`, so the derived parsers will transparently convert errors using the [`Into`] trait.
+
+
+Example:
+```rust
+# use asn1_rs::*;
+#
+#[derive(Debug, PartialEq)]
+pub enum MyError {
+    NotYetImplemented,
+}
+
+impl From<asn1_rs::Error> for MyError {
+    fn from(_: asn1_rs::Error) -> Self {
+        MyError::NotYetImplemented
+    }
+}
+
+#[derive(DerSequence)]
+#[error(MyError)]
+pub struct T2 {
+    pub a: u32,
+}
+```
+
+## Mapping errors
+
+Sometimes, it is necessary to map the returned error to another type, for example when a subparser returns a different error type than the parser's, and the [`Into`] trait cannot be implemented. This is often used in combination with the `error` attribute, but can also be used alone.
+
+The `map_err` attribute can be used to specify a function or closure to map errors. The function signature is `fn (e1: E1) -> E2`.
+
+Example:
+```rust
+# use asn1_rs::*;
+#
+#[derive(Debug, PartialEq)]
+pub enum MyError {
+    NotYetImplemented,
+}
+
+impl From<asn1_rs::Error> for MyError {
+    fn from(_: asn1_rs::Error) -> Self {
+        MyError::NotYetImplemented
+    }
+}
+
+#[derive(DerSequence)]
+#[error(MyError)]
+pub struct T2 {
+    pub a: u32,
+}
+
+// subparser returns an error of type MyError,
+// which is mapped to `Error`
+#[derive(DerSequence)]
+pub struct T4 {
+    #[map_err(|_| Error::BerTypeError)]
+    pub a: T2,
+}
+```
+
+*Note*: when deriving BER and DER parsers, errors paths are different (`TryFrom` returns the error type, while [`FromDer`] returns a [`ParseResult`]). Some code will be inserted by the `map_err` attribute to handle this transparently and keep the same function signature.
+
+[`FromBer`]: crate::FromBer
+[`FromDer`]: crate::FromDer
+[`BerSequence`]: crate::BerSequence
+[`DerSequence`]: crate::DerSequence
+[`BerSet`]: crate::BerSet
+[`DerSet`]: crate::DerSet
+[`ParseResult`]: crate::ParseResult
+[`TaggedExplicit`]: crate::TaggedExplicit
+[`TaggedImplicit`]: crate::TaggedImplicit
+[`TaggedValue`]: crate::TaggedValue
diff --git a/doc/RECIPES.md b/doc/RECIPES.md
new file mode 100644
index 0000000..b053c6d
--- /dev/null
+++ b/doc/RECIPES.md
@@ -0,0 +1,238 @@
+# Documentation: BER/DER parsing recipes
+
+## Builtin types
+
+Most builtin types can be parsed by calling the `from_der` or `from_der` functions (see `FromBer` and `FromDer` traits for documentation).
+
+For ex:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = <u32>::from_der(input)?;
+# Ok(()) };
+```
+
+Note: this crates makes extensive use of types annotation and turbofish operator, for example `<Type>::from_der()` or `TaggedExplicit::<u32, Error, 0>::from_der()`.
+
+See table B-3 in <https://doc.rust-lang.org/book/appendix-02-operators.html> for reference on syntax.
+
+## `SEQUENCE` and `SET`
+
+The `SEQUENCE` and `SET` types are handled very similarly, so recipes will be given for `SEQUENCE`, but can be adapted to `SET` by replacing words.
+
+### Parsing `SEQUENCE`
+
+Usually, the sequence envelope does not need to be stored, so it just needs to be parsed to get the sequence content and parse it.
+The methods [`from_ber_and_then`](crate::Sequence::from_ber_and_then()) and [`from_der_and_then`](crate::Sequence::from_der_and_then()) provide helpers for that:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = Sequence::from_ber_and_then(input, |i| {
+    // first item is INTEGER
+    let (rem, a) = u32::from_der(input)?;
+    // second item is OCTET STRING
+    let (rem, b) = <&[u8]>::from_der(input)?;
+    Ok((rem, (a, b)))
+})?;
+// result has type (u32, &[u8])
+assert_eq!(result.0, 0);
+assert_eq!(result.1, b"\x00\x01");
+# Ok(()) };
+```
+
+### Automatically deriving sequence parsers
+
+The [`BerSequence`](crate::BerSequence) and [`DerSequence`](crate::DerSequence)
+custom derive provide attributes to automatically derive a parser for a sequence.
+
+For ex:
+
+```rust
+# use asn1_rs::*;
+#[derive(DerSequence)]
+pub struct S {
+    a: u32,
+    b: u16,
+    c: u16,
+}
+
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = S::from_der(input)?;
+# Ok(()) };
+```
+
+This will work for any field type that implements [`FromBer`](crate::FromBer) or [`FromDer`](crate::FromDer), respectively.
+
+See [`derive`](mod@derive) documentation for more examples and documentation.
+
+### Parsing `SEQUENCE OF`
+
+`SEQUENCE OF T` can be parsed using either type `SequenceOf<T>` or `Vec<T>`:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = SequenceOf::<u32>::from_der(input)?;
+# Ok(()) };
+```
+
+or
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = <Vec<u32>>::from_der(input)?;
+# Ok(()) };
+```
+
+`SET OF T` can be parsed using either `SetOf<T>`, `BTreeSet<T>` or `HashSet<T>`.
+
+## `EXPLICIT` tagged values
+
+### Parsing `EXPLICIT`, expecting a known tag
+
+If you expect only a specific tag, use `TaggedExplicit`.
+
+For ex, to parse a `[3] EXPLICIT INTEGER`:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = TaggedExplicit::<u32, Error, 0>::from_der(input)?;
+// result has type TaggedValue. Use `.as_ref()` or `.into_inner()` 
+// to access content
+let tag = result.tag();
+let class = result.class();
+assert_eq!(result.as_ref(), &0);
+# Ok(()) };
+```
+
+### Specifying the class
+
+`TaggedExplicit` does not check the class, and accepts any class. It expects you to check the class after reading the value.
+
+
+To specify the class in the parser, use `TaggedValue`:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+// Note: the strange notation (using braces) is required by the compiler to use
+// a constant instead of the numeric value.
+let (rem, result) = TaggedValue::<u32, Error, Explicit, {Class::CONTEXT_SPECIFIC}, 0>::from_der(input)?;
+# Ok(()) };
+```
+
+Note that `TaggedExplicit` is a type alias to `TaggedValue`, so the objects are the same.
+
+### Accepting any `EXPLICIT` tag
+
+To parse a value, accepting any class or tag, use `TaggedParser`.
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = TaggedParser::<Explicit, u32>::from_der(input)?;
+// result has type TaggedParser. Use `.as_ref()` or `.into_inner()` 
+// to access content
+let tag = result.tag();
+let class = result.class();
+assert_eq!(result.as_ref(), &0);
+# Ok(()) };
+```
+
+### Optional tagged values
+
+To parse optional tagged values, `Option<TaggedExplicit<...>>` can be used:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = Option::<TaggedExplicit::<u32, Error, 0>>::from_der(input)?;
+# Ok(()) };
+```
+
+The type `OptTaggedExplicit` is also provided as an alias:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = OptTaggedExplicit::<u32, Error, 0>::from_der(input)?;
+# Ok(()) };
+```
+
+## `IMPLICIT` tagged values
+
+### Parsing `IMPLICIT`, expecting a known tag
+
+If you expect only a specific tag, use `TaggedImplicit`.
+
+For ex, to parse a `[3] EXPLICIT INTEGER`:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = TaggedExplicit::<u32, Error, 0>::from_der(input)?;
+// result has type TaggedValue. Use `.as_ref()` or `.into_inner()` 
+// to access content
+let tag = result.tag();
+let class = result.class();
+assert_eq!(result.as_ref(), &0);
+# Ok(()) };
+```
+
+### Specifying the class
+
+`TaggedImplicit` does not check the class, and accepts any class. It expects you to check the class after reading the value.
+
+
+To specify the class in the parser, use `TaggedValue`:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+// Note: the strange notation (using braces) is required by the compiler to use
+// a constant instead of the numeric value.
+let (rem, result) = TaggedValue::<u32, Error, Implicit, { Class::CONTEXT_SPECIFIC }, 1>::from_der(input)?;
+# Ok(()) };
+```
+
+Note that `TaggedImplicit` is a type alias to `TaggedValue`, so the objects are the same.
+
+### Accepting any `IMPLICIT` tag
+
+To parse a value, accepting any class or tag, use `TaggedParser`.
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = TaggedParser::<Implicit, u32>::from_der(input)?;
+// result has type TaggedParser. Use `.as_ref()` or `.into_inner()` 
+// to access content
+let tag = result.tag();
+let class = result.class();
+assert_eq!(result.as_ref(), &0);
+# Ok(()) };
+```
+
+### Optional tagged values
+
+To parse optional tagged values, `Option<TaggedImplicit<...>>` can be used:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = Option::<TaggedImplicit::<u32, Error, 0>>::from_der(input)?;
+# Ok(()) };
+```
+
+The type `OptTaggedImplicit` is also provided as an alias:
+
+```rust
+# use asn1_rs::*;
+# let parser = |input| -> Result<(), Error> {
+let (rem, result) = OptTaggedImplicit::<u32, Error, 0>::from_der(input)?;
+# Ok(()) };
+```
diff --git a/examples/dump-der.rs b/examples/dump-der.rs
new file mode 100644
index 0000000..29ee897
--- /dev/null
+++ b/examples/dump-der.rs
@@ -0,0 +1,243 @@
+use asn1_rs::{Any, Class, FromDer, Length, Result, Tag};
+use colored::*;
+use nom::HexDisplay;
+use oid_registry::{format_oid, Oid as DerOid, OidRegistry};
+use std::cmp::min;
+use std::error::Error;
+use std::marker::PhantomData;
+use std::{env, fs};
+
+struct Context<'a> {
+    oid_registry: OidRegistry<'a>,
+    hex_max: usize,
+    t: PhantomData<&'a ()>,
+}
+
+impl<'a> Default for Context<'a> {
+    fn default() -> Self {
+        let oid_registry = OidRegistry::default().with_all_crypto().with_x509();
+        Context {
+            oid_registry,
+            hex_max: 64,
+            t: PhantomData,
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! indent_println {
+    ( $depth: expr, $fmt:expr ) => {
+        println!(concat!("{:indent$}",$fmt), "", indent = 2*$depth)
+    };
+    ( $depth: expr, $fmt:expr, $( $x:expr ),* ) => {
+        println!(concat!("{:indent$}",$fmt), "", $($x),*, indent = 2*$depth)
+    };
+}
+
+#[allow(dead_code)]
+pub fn print_hex_dump(bytes: &[u8], max_len: usize) {
+    let m = min(bytes.len(), max_len);
+    print!("{}", &bytes[..m].to_hex(16));
+    if bytes.len() > max_len {
+        println!("... <continued>");
+    }
+}
+
+fn main() -> std::result::Result<(), Box<dyn Error>> {
+    let ctx = Context::default();
+    for filename in env::args().skip(1) {
+        eprintln!("File: {}", filename);
+        let content = fs::read(&filename)?;
+        // check for PEM file
+        if filename.ends_with(".pem") || content.starts_with(b"----") {
+            let pems = pem::parse_many(&content).expect("Parsing PEM failed");
+            if pems.is_empty() {
+                eprintln!("{}", "No PEM section decoded".bright_red());
+                continue;
+            }
+            for (idx, pem) in pems.iter().enumerate() {
+                eprintln!("Pem entry {} [{}]", idx, pem.tag.bright_blue());
+                print_der(&pem.contents, 1, &ctx);
+            }
+        } else {
+            print_der(&content, 1, &ctx);
+        }
+    }
+
+    Ok(())
+}
+
+fn print_der(i: &[u8], depth: usize, ctx: &Context) {
+    match Any::from_der(i) {
+        Ok((rem, any)) => {
+            print_der_any(any, depth, ctx);
+            if !rem.is_empty() {
+                let warning = format!("WARNING: {} extra bytes after object", rem.len());
+                indent_println!(depth, "{}", warning.bright_red());
+                print_hex_dump(rem, ctx.hex_max);
+            }
+        }
+        Err(e) => {
+            eprintln!("Error while parsing at depth {}: {:?}", depth, e);
+        }
+    }
+}
+
+fn print_der_result_any(r: Result<Any>, depth: usize, ctx: &Context) {
+    match r {
+        Ok(any) => print_der_any(any, depth, ctx),
+        Err(e) => {
+            eprintln!("Error while parsing at depth {}: {:?}", depth, e);
+        }
+    }
+}
+
+fn print_der_any(any: Any, depth: usize, ctx: &Context) {
+    let class = match any.header.class() {
+        Class::Universal => "UNIVERSAL".to_string().white(),
+        c => c.to_string().cyan(),
+    };
+    let hdr = format!(
+        "[c:{} t:{}({}) l:{}]",
+        class,
+        any.header.tag().0,
+        any.header.tag().to_string().white(),
+        str_of_length(any.header.length())
+    );
+    indent_println!(depth, "{}", hdr);
+    match any.header.class() {
+        Class::Universal => (),
+        Class::ContextSpecific | Class::Application => {
+            // attempt to decode inner object (if EXPLICIT)
+            match Any::from_der(any.data) {
+                Ok((rem2, inner)) => {
+                    indent_println!(
+                        depth + 1,
+                        "{} (rem.len={})",
+                        format!("EXPLICIT [{}]", any.header.tag().0).green(),
+                        // any.header.tag.0,
+                        rem2.len()
+                    );
+                    print_der_any(inner, depth + 2, ctx);
+                }
+                Err(_) => {
+                    // assume tagged IMPLICIT
+                    indent_println!(
+                        depth + 1,
+                        "{}",
+                        "could not decode (IMPLICIT tagging?)".bright_red()
+                    );
+                }
+            }
+            return;
+        }
+        _ => {
+            indent_println!(
+                depth + 1,
+                "tagged: [{}] {}",
+                any.header.tag().0,
+                "*NOT SUPPORTED*".red()
+            );
+            return;
+        }
+    }
+    match any.header.tag() {
+        Tag::BitString => {
+            let b = any.bitstring().unwrap();
+            indent_println!(depth + 1, "BITSTRING");
+            print_hex_dump(b.as_ref(), ctx.hex_max);
+        }
+        Tag::Boolean => {
+            let b = any.bool().unwrap();
+            indent_println!(depth + 1, "BOOLEAN: {}", b.to_string().green());
+        }
+        Tag::EmbeddedPdv => {
+            let e = any.embedded_pdv().unwrap();
+            indent_println!(depth + 1, "EMBEDDED PDV: {:?}", e);
+            print_hex_dump(e.data_value, ctx.hex_max);
+        }
+        Tag::Enumerated => {
+            let i = any.enumerated().unwrap();
+            indent_println!(depth + 1, "ENUMERATED: {}", i.0);
+        }
+        Tag::GeneralizedTime => {
+            let s = any.generalizedtime().unwrap();
+            indent_println!(depth + 1, "GeneralizedTime: {}", s);
+        }
+        Tag::GeneralString => {
+            let s = any.generalstring().unwrap();
+            indent_println!(depth + 1, "GeneralString: {}", s.as_ref());
+        }
+        Tag::Ia5String => {
+            let s = any.ia5string().unwrap();
+            indent_println!(depth + 1, "IA5String: {}", s.as_ref());
+        }
+        Tag::Integer => {
+            let i = any.integer().unwrap();
+            match i.as_i128() {
+                Ok(i) => {
+                    indent_println!(depth + 1, "{}", i);
+                }
+                Err(_) => {
+                    print_hex_dump(i.as_ref(), ctx.hex_max);
+                }
+            }
+        }
+        Tag::Null => (),
+        Tag::OctetString => {
+            let b = any.octetstring().unwrap();
+            indent_println!(depth + 1, "OCTETSTRING");
+            print_hex_dump(b.as_ref(), ctx.hex_max);
+        }
+        Tag::Oid => {
+            let oid = any.oid().unwrap();
+            let der_oid = DerOid::new(oid.as_bytes().into());
+            indent_println!(
+                depth + 1,
+                "OID: {}",
+                format_oid(&der_oid, &ctx.oid_registry).cyan()
+            );
+        }
+        Tag::PrintableString => {
+            let s = any.printablestring().unwrap();
+            indent_println!(depth + 1, "PrintableString: {}", s.as_ref());
+        }
+        Tag::RelativeOid => {
+            let oid = any.oid().unwrap();
+            let der_oid = DerOid::new(oid.as_bytes().into());
+            indent_println!(
+                depth + 1,
+                "RELATIVE-OID: {}",
+                format_oid(&der_oid, &ctx.oid_registry).cyan()
+            );
+        }
+        Tag::Set => {
+            let seq = any.set().unwrap();
+            for item in seq.der_iter::<Any, asn1_rs::Error>() {
+                print_der_result_any(item, depth + 1, ctx);
+            }
+        }
+        Tag::Sequence => {
+            let seq = any.sequence().unwrap();
+            for item in seq.der_iter::<Any, asn1_rs::Error>() {
+                print_der_result_any(item, depth + 1, ctx);
+            }
+        }
+        Tag::UtcTime => {
+            let s = any.utctime().unwrap();
+            indent_println!(depth + 1, "UtcTime: {}", s);
+        }
+        Tag::Utf8String => {
+            let s = any.utf8string().unwrap();
+            indent_println!(depth + 1, "UTF-8: {}", s.as_ref());
+        }
+        _ => unimplemented!("unsupported tag {}", any.header.tag()),
+    }
+}
+
+fn str_of_length(l: Length) -> String {
+    match l {
+        Length::Definite(l) => l.to_string(),
+        Length::Indefinite => "Indefinite".to_string(),
+    }
+}
diff --git a/src/asn1_types/any.rs b/src/asn1_types/any.rs
new file mode 100644
index 0000000..8628d7d
--- /dev/null
+++ b/src/asn1_types/any.rs
@@ -0,0 +1,429 @@
+use crate::ber::*;
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::String;
+use core::convert::{TryFrom, TryInto};
+
+/// The `Any` object is not strictly an ASN.1 type, but holds a generic description of any object
+/// that could be encoded.
+///
+/// It contains a header, and either a reference to or owned data for the object content.
+///
+/// Note: this type is only provided in **borrowed** version (*i.e.* it cannot own the inner data).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Any<'a> {
+    /// The object header
+    pub header: Header<'a>,
+    /// The object contents
+    pub data: &'a [u8],
+}
+
+impl<'a> Any<'a> {
+    /// Create a new `Any` from BER/DER header and content
+    #[inline]
+    pub const fn new(header: Header<'a>, data: &'a [u8]) -> Self {
+        Any { header, data }
+    }
+
+    /// Create a new `Any` from a tag, and BER/DER content
+    #[inline]
+    pub const fn from_tag_and_data(tag: Tag, data: &'a [u8]) -> Self {
+        let constructed = matches!(tag, Tag::Sequence | Tag::Set);
+        Any {
+            header: Header {
+                tag,
+                constructed,
+                class: Class::Universal,
+                length: Length::Definite(data.len()),
+                raw_tag: None,
+            },
+            data,
+        }
+    }
+
+    /// Return the `Class` of this object
+    #[inline]
+    pub const fn class(&self) -> Class {
+        self.header.class
+    }
+
+    /// Update the class of the current object
+    #[inline]
+    pub fn with_class(self, class: Class) -> Self {
+        Any {
+            header: self.header.with_class(class),
+            ..self
+        }
+    }
+
+    /// Return the `Tag` of this object
+    #[inline]
+    pub const fn tag(&self) -> Tag {
+        self.header.tag
+    }
+
+    /// Update the tag of the current object
+    #[inline]
+    pub fn with_tag(self, tag: Tag) -> Self {
+        Any {
+            header: self.header.with_tag(tag),
+            data: self.data,
+        }
+    }
+
+    /// Get the bytes representation of the *content*
+    #[inline]
+    pub fn as_bytes(&'a self) -> &'a [u8] {
+        self.data
+    }
+
+    #[inline]
+    pub fn parse_ber<T>(&'a self) -> ParseResult<'a, T>
+    where
+        T: FromBer<'a>,
+    {
+        T::from_ber(self.data)
+    }
+
+    /// Parse a BER value and apply the provided parsing function to content
+    ///
+    /// After parsing, the sequence object and header are discarded.
+    pub fn from_ber_and_then<F, T, E>(
+        class: Class,
+        tag: u32,
+        bytes: &'a [u8],
+        op: F,
+    ) -> ParseResult<'a, T, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+        E: From<Error>,
+    {
+        let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Tag(tag))
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        any.class()
+            .assert_eq(class)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let (_, res) = op(any.data)?;
+        Ok((rem, res))
+    }
+
+    /// Parse a DER value and apply the provided parsing function to content
+    ///
+    /// After parsing, the sequence object and header are discarded.
+    pub fn from_der_and_then<F, T, E>(
+        class: Class,
+        tag: u32,
+        bytes: &'a [u8],
+        op: F,
+    ) -> ParseResult<'a, T, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+        E: From<Error>,
+    {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Tag(tag))
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        any.class()
+            .assert_eq(class)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let (_, res) = op(any.data)?;
+        Ok((rem, res))
+    }
+
+    #[inline]
+    pub fn parse_der<T>(&'a self) -> ParseResult<'a, T>
+    where
+        T: FromDer<'a>,
+    {
+        T::from_der(self.data)
+    }
+
+    /// Get the content following a BER header
+    #[inline]
+    pub fn parse_ber_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> {
+        header.parse_ber_content(i)
+    }
+
+    /// Get the content following a DER header
+    #[inline]
+    pub fn parse_der_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> {
+        header.assert_definite()?;
+        DerParser::get_object_content(i, header, 8)
+    }
+}
+
+macro_rules! impl_any_into {
+    (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => {
+        #[doc = "Attempt to convert object to `"]
+        #[doc = $sname]
+        #[doc = "` (ASN.1 type: `"]
+        #[doc = $asn1]
+        #[doc = "`)."]
+        pub fn $fn_name(self) -> Result<$ty> {
+            self.try_into()
+        }
+    };
+    ($fn_name:ident => $ty:ty, $asn1:expr) => {
+        impl_any_into! {
+            IMPL stringify!($ty), $fn_name => $ty, $asn1
+        }
+    };
+}
+
+macro_rules! impl_any_as {
+    (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => {
+        #[doc = "Attempt to create ASN.1 type `"]
+        #[doc = $asn1]
+        #[doc = "` from this object."]
+        #[inline]
+        pub fn $fn_name(&self) -> Result<$ty> {
+            TryFrom::try_from(self)
+        }
+    };
+    ($fn_name:ident => $ty:ty, $asn1:expr) => {
+        impl_any_as! {
+            IMPL stringify!($ty), $fn_name => $ty, $asn1
+        }
+    };
+}
+
+impl<'a> Any<'a> {
+    impl_any_into!(bitstring => BitString<'a>, "BIT STRING");
+    impl_any_into!(bmpstring => BmpString<'a>, "BmpString");
+    impl_any_into!(bool => bool, "BOOLEAN");
+    impl_any_into!(boolean => Boolean, "BOOLEAN");
+    impl_any_into!(embedded_pdv => EmbeddedPdv<'a>, "EMBEDDED PDV");
+    impl_any_into!(enumerated => Enumerated, "ENUMERATED");
+    impl_any_into!(generalizedtime => GeneralizedTime, "GeneralizedTime");
+    impl_any_into!(generalstring => GeneralString<'a>, "GeneralString");
+    impl_any_into!(graphicstring => GraphicString<'a>, "GraphicString");
+    impl_any_into!(i8 => i8, "INTEGER");
+    impl_any_into!(i16 => i16, "INTEGER");
+    impl_any_into!(i32 => i32, "INTEGER");
+    impl_any_into!(i64 => i64, "INTEGER");
+    impl_any_into!(i128 => i128, "INTEGER");
+    impl_any_into!(ia5string => Ia5String<'a>, "IA5String");
+    impl_any_into!(integer => Integer<'a>, "INTEGER");
+    impl_any_into!(null => Null, "NULL");
+    impl_any_into!(numericstring => NumericString<'a>, "NumericString");
+    impl_any_into!(objectdescriptor => ObjectDescriptor<'a>, "ObjectDescriptor");
+    impl_any_into!(octetstring => OctetString<'a>, "OCTET STRING");
+    impl_any_into!(oid => Oid<'a>, "OBJECT IDENTIFIER");
+    /// Attempt to convert object to `Oid` (ASN.1 type: `RELATIVE-OID`).
+    pub fn relative_oid(self) -> Result<Oid<'a>> {
+        self.header.assert_tag(Tag::RelativeOid)?;
+        let asn1 = Cow::Borrowed(self.data);
+        Ok(Oid::new_relative(asn1))
+    }
+    impl_any_into!(printablestring => PrintableString<'a>, "PrintableString");
+    // XXX REAL
+    impl_any_into!(sequence => Sequence<'a>, "SEQUENCE");
+    impl_any_into!(set => Set<'a>, "SET");
+    impl_any_into!(str => &'a str, "UTF8String");
+    impl_any_into!(string => String, "UTF8String");
+    impl_any_into!(teletexstring => TeletexString<'a>, "TeletexString");
+    impl_any_into!(u8 => u8, "INTEGER");
+    impl_any_into!(u16 => u16, "INTEGER");
+    impl_any_into!(u32 => u32, "INTEGER");
+    impl_any_into!(u64 => u64, "INTEGER");
+    impl_any_into!(u128 => u128, "INTEGER");
+    impl_any_into!(universalstring => UniversalString<'a>, "UniversalString");
+    impl_any_into!(utctime => UtcTime, "UTCTime");
+    impl_any_into!(utf8string => Utf8String<'a>, "UTF8String");
+    impl_any_into!(videotexstring => VideotexString<'a>, "VideotexString");
+    impl_any_into!(visiblestring => VisibleString<'a>, "VisibleString");
+
+    impl_any_as!(as_bitstring => BitString, "BITSTRING");
+    impl_any_as!(as_bool => bool, "BOOLEAN");
+    impl_any_as!(as_boolean => Boolean, "BOOLEAN");
+    impl_any_as!(as_embedded_pdv => EmbeddedPdv, "EMBEDDED PDV");
+    impl_any_as!(as_endofcontent => EndOfContent, "END OF CONTENT (not a real ASN.1 type)");
+    impl_any_as!(as_enumerated => Enumerated, "ENUMERATED");
+    impl_any_as!(as_generalizedtime => GeneralizedTime, "GeneralizedTime");
+    impl_any_as!(as_generalstring => GeneralizedTime, "GeneralString");
+    impl_any_as!(as_graphicstring => GraphicString, "GraphicString");
+    impl_any_as!(as_i8 => i8, "INTEGER");
+    impl_any_as!(as_i16 => i16, "INTEGER");
+    impl_any_as!(as_i32 => i32, "INTEGER");
+    impl_any_as!(as_i64 => i64, "INTEGER");
+    impl_any_as!(as_i128 => i128, "INTEGER");
+    impl_any_as!(as_ia5string => Ia5String, "IA5String");
+    impl_any_as!(as_integer => Integer, "INTEGER");
+    impl_any_as!(as_null => Null, "NULL");
+    impl_any_as!(as_numericstring => NumericString, "NumericString");
+    impl_any_as!(as_objectdescriptor => ObjectDescriptor, "OBJECT IDENTIFIER");
+    impl_any_as!(as_octetstring => OctetString, "OCTET STRING");
+    impl_any_as!(as_oid => Oid, "OBJECT IDENTIFIER");
+    /// Attempt to create ASN.1 type `RELATIVE-OID` from this object.
+    pub fn as_relative_oid(&self) -> Result<Oid<'a>> {
+        self.header.assert_tag(Tag::RelativeOid)?;
+        let asn1 = Cow::Borrowed(self.data);
+        Ok(Oid::new_relative(asn1))
+    }
+    impl_any_as!(as_printablestring => PrintableString, "PrintableString");
+    impl_any_as!(as_sequence => Sequence, "SEQUENCE");
+    impl_any_as!(as_set => Set, "SET");
+    impl_any_as!(as_str => &str, "UTF8String");
+    impl_any_as!(as_string => String, "UTF8String");
+    impl_any_as!(as_teletexstring => TeletexString, "TeletexString");
+    impl_any_as!(as_u8 => u8, "INTEGER");
+    impl_any_as!(as_u16 => u16, "INTEGER");
+    impl_any_as!(as_u32 => u32, "INTEGER");
+    impl_any_as!(as_u64 => u64, "INTEGER");
+    impl_any_as!(as_u128 => u128, "INTEGER");
+    impl_any_as!(as_universalstring => UniversalString, "UniversalString");
+    impl_any_as!(as_utctime => UtcTime, "UTCTime");
+    impl_any_as!(as_utf8string => Utf8String, "UTF8String");
+    impl_any_as!(as_videotexstring => VideotexString, "VideotexString");
+    impl_any_as!(as_visiblestring => VisibleString, "VisibleString");
+
+    /// Attempt to create an `Option<T>` from this object.
+    pub fn as_optional<'b, T>(&'b self) -> Result<Option<T>>
+    where
+        T: TryFrom<&'b Any<'a>, Error = Error>,
+        'a: 'b,
+    {
+        match TryFrom::try_from(self) {
+            Ok(t) => Ok(Some(t)),
+            Err(Error::UnexpectedTag { .. }) => Ok(None),
+            Err(e) => Err(e),
+        }
+    }
+
+    /// Attempt to create a tagged value (EXPLICIT) from this object.
+    pub fn as_tagged_explicit<T, E, const CLASS: u8, const TAG: u32>(
+        &self,
+    ) -> Result<TaggedValue<T, E, Explicit, CLASS, TAG>, E>
+    where
+        T: FromBer<'a, E>,
+        E: From<Error>,
+    {
+        TryFrom::try_from(self)
+    }
+
+    /// Attempt to create a tagged value (IMPLICIT) from this object.
+    pub fn as_tagged_implicit<T, E, const CLASS: u8, const TAG: u32>(
+        &self,
+    ) -> Result<TaggedValue<T, E, Implicit, CLASS, TAG>, E>
+    where
+        T: TryFrom<Any<'a>, Error = E>,
+        T: Tagged,
+        E: From<Error>,
+    {
+        TryFrom::try_from(self)
+    }
+}
+
+impl<'a> FromBer<'a> for Any<'a> {
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+        let (i, header) = Header::from_ber(bytes)?;
+        let (i, data) = BerParser::get_object_content(i, &header, MAX_RECURSION)?;
+        Ok((i, Any { header, data }))
+    }
+}
+
+impl<'a> FromDer<'a> for Any<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
+        let (i, header) = Header::from_der(bytes)?;
+        // X.690 section 10.1: The definite form of length encoding shall be used
+        header.length.assert_definite()?;
+        let (i, data) = DerParser::get_object_content(i, &header, MAX_RECURSION)?;
+        Ok((i, Any { header, data }))
+    }
+}
+
+impl CheckDerConstraints for Any<'_> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length().assert_definite()?;
+        // if len < 128, must use short form (10.1: minimum number of octets)
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Any<'_> {}
+
+impl DynTagged for Any<'_> {
+    fn tag(&self) -> Tag {
+        self.tag()
+    }
+}
+
+// impl<'a> ToStatic for Any<'a> {
+//     type Owned = Any<'static>;
+
+//     fn to_static(&self) -> Self::Owned {
+//         Any {
+//             header: self.header.to_static(),
+//             data: Cow::Owned(self.data.to_vec()),
+//         }
+//     }
+// }
+
+#[cfg(feature = "std")]
+impl ToDer for Any<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let hdr_len = self.header.to_der_len()?;
+        Ok(hdr_len + self.data.len())
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // create fake header to have correct length
+        let header = Header::new(
+            self.header.class,
+            self.header.constructed,
+            self.header.tag,
+            Length::Definite(self.data.len()),
+        );
+        let sz = header.write_der_header(writer)?;
+        Ok(sz)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(self.data).map_err(Into::into)
+    }
+
+    /// Similar to using `to_der`, but uses header without computing length value
+    fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let sz = self.header.write_der_header(writer)?;
+        let sz = sz + writer.write(self.data)?;
+        Ok(sz)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use hex_literal::hex;
+
+    #[test]
+    fn methods_any() {
+        let header = Header::new_simple(Tag::Integer);
+        let any = Any::new(header, &[])
+            .with_class(Class::ContextSpecific)
+            .with_tag(Tag(0));
+        assert_eq!(any.as_bytes(), &[]);
+
+        let input = &hex! {"80 03 02 01 01"};
+        let (_, any) = Any::from_ber(input).expect("parsing failed");
+
+        let (_, r) = any.parse_ber::<Integer>().expect("parse_ber failed");
+        assert_eq!(r.as_u32(), Ok(1));
+        let (_, r) = any.parse_der::<Integer>().expect("parse_der failed");
+        assert_eq!(r.as_u32(), Ok(1));
+
+        let header = &any.header;
+        let (_, content) = Any::parse_ber_content(&input[2..], header).unwrap();
+        assert_eq!(content.len(), 3);
+        let (_, content) = Any::parse_der_content(&input[2..], header).unwrap();
+        assert_eq!(content.len(), 3);
+
+        let (_, any) = Any::from_der(&input[2..]).unwrap();
+        Any::check_constraints(&any).unwrap();
+        assert_eq!(<Any as DynTagged>::tag(&any), any.tag());
+        let int = any.integer().unwrap();
+        assert_eq!(int.as_u16(), Ok(1));
+    }
+}
diff --git a/src/asn1_types/bitstring.rs b/src/asn1_types/bitstring.rs
new file mode 100644
index 0000000..fad03cf
--- /dev/null
+++ b/src/asn1_types/bitstring.rs
@@ -0,0 +1,157 @@
+use crate::*;
+use alloc::borrow::Cow;
+#[cfg(feature = "bits")]
+use bitvec::{order::Msb0, slice::BitSlice};
+use core::convert::TryFrom;
+
+/// ASN.1 `BITSTRING` type
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct BitString<'a> {
+    pub unused_bits: u8,
+    pub data: Cow<'a, [u8]>,
+}
+
+impl<'a> BitString<'a> {
+    // Length must be >= 1 (first byte is number of ignored bits)
+    pub const fn new(unused_bits: u8, s: &'a [u8]) -> Self {
+        BitString {
+            unused_bits,
+            data: Cow::Borrowed(s),
+        }
+    }
+
+    /// Test if bit `bitnum` is set
+    pub fn is_set(&self, bitnum: usize) -> bool {
+        let byte_pos = bitnum / 8;
+        if byte_pos >= self.data.len() {
+            return false;
+        }
+        let b = 7 - (bitnum % 8);
+        (self.data[byte_pos] & (1 << b)) != 0
+    }
+
+    /// Constructs a shared `&BitSlice` reference over the object data.
+    #[cfg(feature = "bits")]
+    pub fn as_bitslice(&self) -> Option<&BitSlice<u8, Msb0>> {
+        BitSlice::<_, Msb0>::try_from_slice(&self.data).ok()
+    }
+}
+
+impl<'a> AsRef<[u8]> for BitString<'a> {
+    fn as_ref(&self) -> &[u8] {
+        &self.data
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for BitString<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<BitString<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+// non-consuming version
+impl<'a, 'b> TryFrom<&'b Any<'a>> for BitString<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<BitString<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+        if any.data.is_empty() {
+            return Err(Error::InvalidLength);
+        }
+        let s = any.data;
+        let (unused_bits, data) = (s[0], Cow::Borrowed(&s[1..]));
+        Ok(BitString { unused_bits, data })
+    }
+}
+
+impl<'a> CheckDerConstraints for BitString<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 10.2
+        any.header.assert_primitive()?;
+        // Check that padding bits are all 0 (X.690 section 11.2.1)
+        match any.data.len() {
+            0 => Err(Error::InvalidLength),
+            1 => {
+                // X.690 section 11.2.2 Note 2
+                if any.data[0] == 0 {
+                    Ok(())
+                } else {
+                    Err(Error::InvalidLength)
+                }
+            }
+            len => {
+                let unused_bits = any.data[0];
+                let last_byte = any.data[len - 1];
+                if last_byte.trailing_zeros() < unused_bits as u32 {
+                    return Err(Error::DerConstraintFailed(DerConstraint::UnusedBitsNotZero));
+                }
+
+                Ok(())
+            }
+        }
+    }
+}
+
+impl DerAutoDerive for BitString<'_> {}
+
+impl<'a> Tagged for BitString<'a> {
+    const TAG: Tag = Tag::BitString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for BitString<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.data.len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) +  1 (unused bits) + len
+            Ok(3 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + 1 (unused bits) + len
+            let n = Length::Definite(sz + 1).to_der_len()?;
+            Ok(2 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(1 + self.data.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let sz = writer.write(&[self.unused_bits])?;
+        let sz = sz + writer.write(&self.data)?;
+        Ok(sz)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::BitString;
+
+    #[test]
+    fn test_bitstring_is_set() {
+        let obj = BitString::new(0, &[0x0f, 0x00, 0x40]);
+        assert!(!obj.is_set(0));
+        assert!(obj.is_set(7));
+        assert!(!obj.is_set(9));
+        assert!(obj.is_set(17));
+    }
+
+    #[cfg(feature = "bits")]
+    #[test]
+    fn test_bitstring_to_bitvec() {
+        let obj = BitString::new(0, &[0x0f, 0x00, 0x40]);
+        let bv = obj.as_bitslice().expect("could not get bitslice");
+        assert_eq!(bv.get(0).as_deref(), Some(&false));
+        assert_eq!(bv.get(7).as_deref(), Some(&true));
+        assert_eq!(bv.get(9).as_deref(), Some(&false));
+        assert_eq!(bv.get(17).as_deref(), Some(&true));
+    }
+}
diff --git a/src/asn1_types/boolean.rs b/src/asn1_types/boolean.rs
new file mode 100644
index 0000000..ed620e4
--- /dev/null
+++ b/src/asn1_types/boolean.rs
@@ -0,0 +1,147 @@
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `BOOLEAN` type
+///
+/// BER objects consider any non-zero value as `true`, and `0` as `false`.
+///
+/// DER objects must use value `0x0` (`false`) or `0xff` (`true`).
+#[derive(Debug, PartialEq, Eq)]
+pub struct Boolean {
+    pub value: u8,
+}
+
+impl Boolean {
+    /// `BOOLEAN` object for value `false`
+    pub const FALSE: Boolean = Boolean::new(0);
+    /// `BOOLEAN` object for value `true`
+    pub const TRUE: Boolean = Boolean::new(0xff);
+
+    /// Create a new `Boolean` from the provided logical value.
+    #[inline]
+    pub const fn new(value: u8) -> Self {
+        Boolean { value }
+    }
+
+    /// Return the `bool` value from this object.
+    #[inline]
+    pub const fn bool(&self) -> bool {
+        self.value != 0
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Boolean {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Boolean> {
+        TryFrom::try_from(&any)
+    }
+}
+
+// non-consuming version
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Boolean {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Boolean> {
+        any.tag().assert_eq(Self::TAG)?;
+        // X.690 section 8.2.1:
+        // The encoding of a boolean value shall be primitive. The contents octets shall consist of a single octet
+        if any.header.length != Length::Definite(1) {
+            return Err(Error::InvalidLength);
+        }
+        let value = any.data[0];
+        Ok(Boolean { value })
+    }
+}
+
+impl CheckDerConstraints for Boolean {
+    fn check_constraints(any: &Any) -> Result<()> {
+        let c = any.data[0];
+        // X.690 section 11.1
+        if !(c == 0 || c == 0xff) {
+            return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean));
+        }
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Boolean {}
+
+impl Tagged for Boolean {
+    const TAG: Tag = Tag::Boolean;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Boolean {
+    fn to_der_len(&self) -> Result<usize> {
+        // 3 = 1 (tag) + 1 (length) + 1 (value)
+        Ok(3)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let b = if self.value != 0 { 0xff } else { 0x00 };
+        writer.write(&[b]).map_err(Into::into)
+    }
+
+    /// Similar to using `to_der`, but uses header without computing length value
+    fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let sz = writer.write(&[Self::TAG.0 as u8, 0x01, self.value])?;
+        Ok(sz)
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for bool {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<bool> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for bool {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<bool> {
+        any.tag().assert_eq(Self::TAG)?;
+        let b = Boolean::try_from(any)?;
+        Ok(b.bool())
+    }
+}
+
+impl CheckDerConstraints for bool {
+    fn check_constraints(any: &Any) -> Result<()> {
+        let c = any.data[0];
+        // X.690 section 11.1
+        if !(c == 0 || c == 0xff) {
+            return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean));
+        }
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for bool {}
+
+impl Tagged for bool {
+    const TAG: Tag = Tag::Boolean;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for bool {
+    fn to_der_len(&self) -> Result<usize> {
+        // 3 = 1 (tag) + 1 (length) + 1 (value)
+        Ok(3)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let b = if *self { 0xff } else { 0x00 };
+        writer.write(&[b]).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/choice.rs b/src/asn1_types/choice.rs
new file mode 100644
index 0000000..4bc01cd
--- /dev/null
+++ b/src/asn1_types/choice.rs
@@ -0,0 +1,21 @@
+use crate::{FromBer, FromDer, Tag, Tagged};
+
+pub trait Choice {
+    /// Is the provided [`Tag`] decodable as a variant of this `CHOICE`?
+    fn can_decode(tag: Tag) -> bool;
+}
+
+/// This blanket impl allows any [`Tagged`] type to function as a [`Choice`]
+/// with a single alternative.
+impl<T> Choice for T
+where
+    T: Tagged,
+{
+    fn can_decode(tag: Tag) -> bool {
+        T::TAG == tag
+    }
+}
+
+pub trait BerChoice<'a>: Choice + FromBer<'a> {}
+
+pub trait DerChoice<'a>: Choice + FromDer<'a> {}
diff --git a/src/asn1_types/embedded_pdv.rs b/src/asn1_types/embedded_pdv.rs
new file mode 100644
index 0000000..9452856
--- /dev/null
+++ b/src/asn1_types/embedded_pdv.rs
@@ -0,0 +1,125 @@
+use crate::*;
+use core::convert::TryFrom;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct EmbeddedPdv<'a> {
+    pub identification: PdvIdentification<'a>,
+    pub data_value_descriptor: Option<ObjectDescriptor<'a>>,
+    pub data_value: &'a [u8],
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum PdvIdentification<'a> {
+    Syntaxes {
+        s_abstract: Oid<'a>,
+        s_transfer: Oid<'a>,
+    },
+    Syntax(Oid<'a>),
+    PresentationContextId(Integer<'a>),
+    ContextNegotiation {
+        presentation_context_id: Integer<'a>,
+        presentation_syntax: Oid<'a>,
+    },
+    TransferSyntax(Oid<'a>),
+    Fixed,
+}
+
+impl<'a> TryFrom<Any<'a>> for EmbeddedPdv<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for EmbeddedPdv<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Self> {
+        let data = any.data;
+        // AUTOMATIC TAGS means all values will be tagged (IMPLICIT)
+        // [0] -> identification
+        let (rem, seq0) =
+            TaggedParser::<Explicit, Any>::parse_ber(Class::ContextSpecific, Tag(0), data)?;
+        let inner = seq0.inner;
+        let identification = match inner.tag() {
+            Tag(0) => {
+                // syntaxes SEQUENCE {
+                //     abstract OBJECT IDENTIFIER,
+                //     transfer OBJECT IDENTIFIER
+                // },
+                // AUTOMATIC tags -> implicit! Hopefully, Oid does not check tag value!
+                let (rem, s_abstract) = Oid::from_ber(inner.data)?;
+                let (_, s_transfer) = Oid::from_ber(rem)?;
+                PdvIdentification::Syntaxes {
+                    s_abstract,
+                    s_transfer,
+                }
+            }
+            Tag(1) => {
+                // syntax OBJECT IDENTIFIER
+                let oid = Oid::new(inner.data.into());
+                PdvIdentification::Syntax(oid)
+            }
+            Tag(2) => {
+                // presentation-context-id INTEGER
+                let i = Integer::new(inner.data);
+                PdvIdentification::PresentationContextId(i)
+            }
+            Tag(3) => {
+                // context-negotiation SEQUENCE {
+                //     presentation-context-id INTEGER,
+                //     transfer-syntax OBJECT IDENTIFIER
+                // },
+                // AUTOMATIC tags -> implicit!
+                let (rem, any) = Any::from_ber(inner.data)?;
+                let presentation_context_id = Integer::new(any.data);
+                let (_, presentation_syntax) = Oid::from_ber(rem)?;
+                PdvIdentification::ContextNegotiation {
+                    presentation_context_id,
+                    presentation_syntax,
+                }
+            }
+            Tag(4) => {
+                // transfer-syntax OBJECT IDENTIFIER
+                let oid = Oid::new(inner.data.into());
+                PdvIdentification::TransferSyntax(oid)
+            }
+            Tag(5) => {
+                // fixed NULL
+                PdvIdentification::Fixed
+            }
+            _ => {
+                return Err(inner
+                    .tag()
+                    .invalid_value("Invalid identification tag in EMBEDDED PDV"))
+            }
+        };
+        // [1] -> data-value-descriptor ObjectDescriptor OPTIONAL
+        // *BUT* WITH COMPONENTS data-value-descriptor ABSENT
+        // XXX this should be parse_ber?
+        // let (rem, data_value_descriptor) =
+        //     TaggedOptional::from(1).parse_der(rem, |_, inner| ObjectDescriptor::from_ber(inner))?;
+        let (rem, data_value_descriptor) = (rem, None);
+        // [2] -> data-value OCTET STRING
+        let (_, data_value) =
+            TaggedParser::<Implicit, &[u8]>::parse_ber(Class::ContextSpecific, Tag(2), rem)?;
+        let data_value = data_value.inner;
+        let obj = EmbeddedPdv {
+            identification,
+            data_value_descriptor,
+            data_value,
+        };
+        Ok(obj)
+    }
+}
+
+impl CheckDerConstraints for EmbeddedPdv<'_> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length().assert_definite()?;
+        any.header.assert_constructed()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for EmbeddedPdv<'_> {}
diff --git a/src/asn1_types/end_of_content.rs b/src/asn1_types/end_of_content.rs
new file mode 100644
index 0000000..4a9d291
--- /dev/null
+++ b/src/asn1_types/end_of_content.rs
@@ -0,0 +1,55 @@
+use crate::{Any, Error, Result, Tag, Tagged};
+use core::convert::TryFrom;
+
+/// End-of-contents octets
+///
+/// `EndOfContent` is not a BER type, but represents a marked to indicate the end of contents
+/// of an object, when the length is `Indefinite` (see X.690 section 8.1.5).
+///
+/// This type cannot exist in DER, and so provides no `FromDer`/`ToDer` implementation.
+#[derive(Debug)]
+pub struct EndOfContent {}
+
+impl EndOfContent {
+    pub const fn new() -> Self {
+        EndOfContent {}
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for EndOfContent {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<EndOfContent> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for EndOfContent {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<EndOfContent> {
+        any.tag().assert_eq(Self::TAG)?;
+        if !any.header.length.is_null() {
+            return Err(Error::InvalidLength);
+        }
+        Ok(EndOfContent {})
+    }
+}
+
+impl Tagged for EndOfContent {
+    const TAG: Tag = Tag::EndOfContent;
+}
+
+// impl ToDer for EndOfContent {
+//     fn to_der_len(&self) -> Result<usize> {
+//         Ok(2)
+//     }
+
+//     fn write_der_header(&self, writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> {
+//         writer.write(&[Self::TAG.0 as u8, 0x00]).map_err(Into::into)
+//     }
+
+//     fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> {
+//         Ok(0)
+//     }
+// }
diff --git a/src/asn1_types/enumerated.rs b/src/asn1_types/enumerated.rs
new file mode 100644
index 0000000..5f843a8
--- /dev/null
+++ b/src/asn1_types/enumerated.rs
@@ -0,0 +1,72 @@
+use crate::ber::bytes_to_u64;
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `ENUMERATED` type
+///
+/// # Limitations
+///
+/// Supported values are limited to 0 .. 2^32
+#[derive(Debug, PartialEq, Eq)]
+pub struct Enumerated(pub u32);
+
+impl Enumerated {
+    pub const fn new(value: u32) -> Self {
+        Enumerated(value)
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Enumerated {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Enumerated> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Enumerated {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Enumerated> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_primitive()?;
+        let res_u64 = bytes_to_u64(any.data)?;
+        if res_u64 > (<u32>::MAX as u64) {
+            return Err(Error::IntegerTooLarge);
+        }
+        let value = res_u64 as u32;
+        Ok(Enumerated(value))
+    }
+}
+
+impl CheckDerConstraints for Enumerated {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length.assert_definite()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Enumerated {}
+
+impl Tagged for Enumerated {
+    const TAG: Tag = Tag::Enumerated;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Enumerated {
+    fn to_der_len(&self) -> Result<usize> {
+        Integer::from(self.0).to_der_len()
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let i = Integer::from(self.0);
+        let len = i.data.len();
+        let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let int = Integer::from(self.0);
+        int.write_der_content(writer).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/generalizedtime.rs b/src/asn1_types/generalizedtime.rs
new file mode 100644
index 0000000..6e039d8
--- /dev/null
+++ b/src/asn1_types/generalizedtime.rs
@@ -0,0 +1,303 @@
+use crate::datetime::decode_decimal;
+use crate::*;
+use alloc::format;
+use alloc::string::String;
+use core::convert::TryFrom;
+use core::fmt;
+#[cfg(feature = "datetime")]
+use time::OffsetDateTime;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GeneralizedTime(pub ASN1DateTime);
+
+impl GeneralizedTime {
+    pub const fn new(datetime: ASN1DateTime) -> Self {
+        GeneralizedTime(datetime)
+    }
+
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
+        // X.680 section 42 defines a GeneralizedTime as a VisibleString restricted to:
+        //
+        // a) a string representing the calendar date, as specified in ISO 8601, with a four-digit representation of the
+        //    year, a two-digit representation of the month and a two-digit representation of the day, without use of
+        //    separators, followed by a string representing the time of day, as specified in ISO 8601, without separators
+        //    other than decimal comma or decimal period (as provided for in ISO 8601), and with no terminating Z (as
+        //    provided for in ISO 8601); or
+        // b) the characters in a) above followed by an upper-case letter Z ; or
+        // c) he characters in a) above followed by a string representing a local time differential, as specified in
+        //    ISO 8601, without separators.
+        let (year, month, day, hour, minute, rem) = match bytes {
+            [year1, year2, year3, year4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] =>
+            {
+                let year_hi = decode_decimal(Self::TAG, *year1, *year2)?;
+                let year_lo = decode_decimal(Self::TAG, *year3, *year4)?;
+                let year = (year_hi as u32) * 100 + (year_lo as u32);
+                let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
+                let day = decode_decimal(Self::TAG, *day1, *day2)?;
+                let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
+                let minute = decode_decimal(Self::TAG, *min1, *min2)?;
+                (year, month, day, hour, minute, rem)
+            }
+            _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
+        };
+        if rem.is_empty() {
+            return Err(Self::TAG.invalid_value("malformed time string"));
+        }
+        // check for seconds
+        let (second, rem) = match rem {
+            [sec1, sec2, rem @ ..] => {
+                let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
+                (second, rem)
+            }
+            _ => (0, rem),
+        };
+        if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
+            // eprintln!("GeneralizedTime: time checks failed");
+            // eprintln!(" month:{}", month);
+            // eprintln!(" day:{}", day);
+            // eprintln!(" hour:{}", hour);
+            // eprintln!(" minute:{}", minute);
+            // eprintln!(" second:{}", second);
+            return Err(Self::TAG.invalid_value("time components with invalid values"));
+        }
+        if rem.is_empty() {
+            // case a): no fractional seconds part, and no terminating Z
+            return Ok(GeneralizedTime(ASN1DateTime::new(
+                year,
+                month,
+                day,
+                hour,
+                minute,
+                second,
+                None,
+                ASN1TimeZone::Undefined,
+            )));
+        }
+        // check for fractional seconds
+        let (millisecond, rem) = match rem {
+            [b'.' | b',', rem @ ..] => {
+                let mut fsecond = 0;
+                let mut rem = rem;
+                let mut digits = 0;
+                for idx in 0..=4 {
+                    if rem.is_empty() {
+                        if idx == 0 {
+                            // dot or comma, but no following digit
+                            return Err(Self::TAG.invalid_value(
+                                "malformed time string (dot or comma but no digits)",
+                            ));
+                        }
+                        digits = idx;
+                        break;
+                    }
+                    if idx == 4 {
+                        return Err(
+                            Self::TAG.invalid_value("malformed time string (invalid milliseconds)")
+                        );
+                    }
+                    match rem[0] {
+                        b'0'..=b'9' => {
+                            // cannot overflow, max 4 digits will be read
+                            fsecond = fsecond * 10 + (rem[0] - b'0') as u16;
+                        }
+                        b'Z' | b'+' | b'-' => {
+                            digits = idx;
+                            break;
+                        }
+                        _ => {
+                            return Err(Self::TAG.invalid_value(
+                                "malformed time string (invalid milliseconds/timezone)",
+                            ))
+                        }
+                    }
+                    rem = &rem[1..];
+                }
+                // fix fractional seconds depending on the number of digits
+                // for ex, date "xxxx.3" means 3000 milliseconds, not 3
+                let fsecond = match digits {
+                    1 => fsecond * 100,
+                    2 => fsecond * 10,
+                    _ => fsecond,
+                };
+                (Some(fsecond), rem)
+            }
+            _ => (None, rem),
+        };
+        // check timezone
+        if rem.is_empty() {
+            // case a): fractional seconds part, and no terminating Z
+            return Ok(GeneralizedTime(ASN1DateTime::new(
+                year,
+                month,
+                day,
+                hour,
+                minute,
+                second,
+                millisecond,
+                ASN1TimeZone::Undefined,
+            )));
+        }
+        let tz = match rem {
+            [b'Z'] => ASN1TimeZone::Z,
+            [b'+', h1, h2, m1, m2] => {
+                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+                ASN1TimeZone::Offset(hh as i8, mm as i8)
+            }
+            [b'-', h1, h2, m1, m2] => {
+                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+                ASN1TimeZone::Offset(-(hh as i8), mm as i8)
+            }
+            _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
+        };
+        Ok(GeneralizedTime(ASN1DateTime::new(
+            year,
+            month,
+            day,
+            hour,
+            minute,
+            second,
+            millisecond,
+            tz,
+        )))
+    }
+
+    /// Return a ISO 8601 combined date and time with time zone.
+    #[cfg(feature = "datetime")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+    pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
+        self.0.to_datetime()
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for GeneralizedTime {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<GeneralizedTime> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for GeneralizedTime {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<GeneralizedTime> {
+        any.tag().assert_eq(Self::TAG)?;
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_visible(b: &u8) -> bool {
+            0x20 <= *b && *b <= 0x7f
+        }
+        if !any.data.iter().all(is_visible) {
+            return Err(Error::StringInvalidCharset);
+        }
+
+        GeneralizedTime::from_bytes(any.data)
+    }
+}
+
+impl fmt::Display for GeneralizedTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let dt = &self.0;
+        let fsec = match self.0.millisecond {
+            Some(v) => format!(".{}", v),
+            None => String::new(),
+        };
+        match dt.tz {
+            ASN1TimeZone::Undefined => write!(
+                f,
+                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}",
+                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
+            ),
+            ASN1TimeZone::Z => write!(
+                f,
+                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}Z",
+                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
+            ),
+            ASN1TimeZone::Offset(hh, mm) => {
+                let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
+                write!(
+                    f,
+                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{}{:02}{:02}",
+                    dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec, s, hh, mm
+                )
+            }
+        }
+    }
+}
+
+impl CheckDerConstraints for GeneralizedTime {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 11.7.1: The encoding shall terminate with a "Z"
+        if any.data.last() != Some(&b'Z') {
+            return Err(Error::DerConstraintFailed(DerConstraint::MissingTimeZone));
+        }
+        // X.690 section 11.7.2: The seconds element shall always be present.
+        // XXX
+        // X.690 section 11.7.4: The decimal point element, if present, shall be the point option "."
+        if any.data.iter().any(|&b| b == b',') {
+            return Err(Error::DerConstraintFailed(DerConstraint::MissingSeconds));
+        }
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for GeneralizedTime {}
+
+impl Tagged for GeneralizedTime {
+    const TAG: Tag = Tag::GeneralizedTime;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for GeneralizedTime {
+    fn to_der_len(&self) -> Result<usize> {
+        // data:
+        // - 8 bytes for YYYYMMDD
+        // - 6 for hhmmss in DER (X.690 section 11.7.2)
+        // - (variable) the fractional part, without trailing zeros, with a point "."
+        // - 1 for the character Z in DER (X.690 section 11.7.1)
+        // data length: 15 + fractional part
+        //
+        // thus, length will always be on 1 byte (short length) and
+        // class+structure+tag also on 1
+        //
+        // total: = 1 (class+constructed+tag) + 1 (length) + 15 + fractional
+        let num_digits = match self.0.millisecond {
+            None => 0,
+            Some(v) => 1 + v.to_string().len(),
+        };
+        Ok(2 + 15 + num_digits)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // see above for length value
+        let num_digits = match self.0.millisecond {
+            None => 0,
+            Some(v) => 1 + v.to_string().len() as u8,
+        };
+        writer
+            .write(&[Self::TAG.0 as u8, 15 + num_digits])
+            .map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let fractional = match self.0.millisecond {
+            None => "".to_string(),
+            Some(v) => format!(".{}", v),
+        };
+        let num_digits = fractional.len();
+        write!(
+            writer,
+            "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
+            self.0.year,
+            self.0.month,
+            self.0.day,
+            self.0.hour,
+            self.0.minute,
+            self.0.second,
+            fractional,
+        )?;
+        // write_fmt returns (), see above for length value
+        Ok(15 + num_digits)
+    }
+}
diff --git a/src/asn1_types/integer.rs b/src/asn1_types/integer.rs
new file mode 100644
index 0000000..59f846a
--- /dev/null
+++ b/src/asn1_types/integer.rs
@@ -0,0 +1,712 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec;
+use core::convert::{TryFrom, TryInto};
+
+#[cfg(feature = "bigint")]
+#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+pub use num_bigint::{BigInt, BigUint, Sign};
+
+/// Decode an unsigned integer into a big endian byte slice with all leading
+/// zeroes removed (if positive) and extra 0xff remove (if negative)
+fn trim_slice<'a>(any: &'a Any<'_>) -> Result<&'a [u8]> {
+    let bytes = any.data;
+
+    if bytes.is_empty() || (bytes[0] != 0x00 && bytes[0] != 0xff) {
+        return Ok(bytes);
+    }
+
+    match bytes.iter().position(|&b| b != 0) {
+        // first byte is not 0
+        Some(0) => (),
+        // all bytes are 0
+        None => return Ok(&bytes[bytes.len() - 1..]),
+        Some(first) => return Ok(&bytes[first..]),
+    }
+
+    // same for negative integers : skip byte 0->n if byte 0->n = 0xff AND byte n+1 >= 0x80
+    match bytes.windows(2).position(|s| match s {
+        &[a, b] => !(a == 0xff && b >= 0x80),
+        _ => true,
+    }) {
+        // first byte is not 0xff
+        Some(0) => (),
+        // all bytes are 0xff
+        None => return Ok(&bytes[bytes.len() - 1..]),
+        Some(first) => return Ok(&bytes[first..]),
+    }
+
+    Ok(bytes)
+}
+
+/// Decode an unsigned integer into a byte array of the requested size
+/// containing a big endian integer.
+fn decode_array_uint<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> {
+    if is_highest_bit_set(any.data) {
+        return Err(Error::IntegerNegative);
+    }
+    let input = trim_slice(any)?;
+
+    if input.len() > N {
+        return Err(Error::IntegerTooLarge);
+    }
+
+    // Input has leading zeroes removed, so we need to add them back
+    let mut output = [0u8; N];
+    assert!(input.len() <= N);
+    output[N.saturating_sub(input.len())..].copy_from_slice(input);
+    Ok(output)
+}
+
+/// Decode an unsigned integer of the specified size.
+///
+/// Returns a byte array of the requested size containing a big endian integer.
+fn decode_array_int<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> {
+    if any.data.len() > N {
+        return Err(Error::IntegerTooLarge);
+    }
+
+    // any.tag().assert_eq(Tag::Integer)?;
+    let mut output = [0xFFu8; N];
+    let offset = N.saturating_sub(any.as_bytes().len());
+    output[offset..].copy_from_slice(any.as_bytes());
+    Ok(output)
+}
+
+/// Is the highest bit of the first byte in the slice 1? (if present)
+#[inline]
+fn is_highest_bit_set(bytes: &[u8]) -> bool {
+    bytes
+        .first()
+        .map(|byte| byte & 0b10000000 != 0)
+        .unwrap_or(false)
+}
+
+macro_rules! impl_int {
+    ($uint:ty => $int:ty) => {
+        impl<'a> TryFrom<Any<'a>> for $int {
+            type Error = Error;
+
+            fn try_from(any: Any<'a>) -> Result<Self> {
+                TryFrom::try_from(&any)
+            }
+        }
+
+        impl<'a, 'b> TryFrom<&'b Any<'a>> for $int {
+            type Error = Error;
+
+            fn try_from(any: &'b Any<'a>) -> Result<Self> {
+                any.tag().assert_eq(Self::TAG)?;
+                any.header.assert_primitive()?;
+                let uint = if is_highest_bit_set(any.as_bytes()) {
+                    <$uint>::from_be_bytes(decode_array_int(&any)?)
+                } else {
+                    // read as uint, but check if the value will fit in a signed integer
+                    let u = <$uint>::from_be_bytes(decode_array_uint(&any)?);
+                    if u > <$int>::MAX as $uint {
+                        return Err(Error::IntegerTooLarge);
+                    }
+                    u
+                };
+                Ok(uint as $int)
+            }
+        }
+
+        impl CheckDerConstraints for $int {
+            fn check_constraints(any: &Any) -> Result<()> {
+                check_der_int_constraints(any)
+            }
+        }
+
+        impl DerAutoDerive for $int {}
+
+        impl Tagged for $int {
+            const TAG: Tag = Tag::Integer;
+        }
+
+        #[cfg(feature = "std")]
+        impl ToDer for $int {
+            fn to_der_len(&self) -> Result<usize> {
+                let int = Integer::from(*self);
+                int.to_der_len()
+            }
+
+            fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der(writer)
+            }
+
+            fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der_header(writer)
+            }
+
+            fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der_content(writer)
+            }
+        }
+    };
+}
+
+macro_rules! impl_uint {
+    ($ty:ty) => {
+        impl<'a> TryFrom<Any<'a>> for $ty {
+            type Error = Error;
+
+            fn try_from(any: Any<'a>) -> Result<Self> {
+                TryFrom::try_from(&any)
+            }
+        }
+        impl<'a, 'b> TryFrom<&'b Any<'a>> for $ty {
+            type Error = Error;
+
+            fn try_from(any: &'b Any<'a>) -> Result<Self> {
+                any.tag().assert_eq(Self::TAG)?;
+                any.header.assert_primitive()?;
+                let result = Self::from_be_bytes(decode_array_uint(any)?);
+                Ok(result)
+            }
+        }
+        impl CheckDerConstraints for $ty {
+            fn check_constraints(any: &Any) -> Result<()> {
+                check_der_int_constraints(any)
+            }
+        }
+
+        impl DerAutoDerive for $ty {}
+
+        impl Tagged for $ty {
+            const TAG: Tag = Tag::Integer;
+        }
+
+        #[cfg(feature = "std")]
+        impl ToDer for $ty {
+            fn to_der_len(&self) -> Result<usize> {
+                let int = Integer::from(*self);
+                int.to_der_len()
+            }
+
+            fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der(writer)
+            }
+
+            fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der_header(writer)
+            }
+
+            fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+                let int = Integer::from(*self);
+                int.write_der_content(writer)
+            }
+        }
+    };
+}
+
+impl_uint!(u8);
+impl_uint!(u16);
+impl_uint!(u32);
+impl_uint!(u64);
+impl_uint!(u128);
+impl_int!(u8 => i8);
+impl_int!(u16 => i16);
+impl_int!(u32 => i32);
+impl_int!(u64 => i64);
+impl_int!(u128 => i128);
+
+/// ASN.1 `INTEGER` type
+///
+/// Generic representation for integer types.
+/// BER/DER integers can be of any size, so it is not possible to store them as simple integers (they
+/// are stored as raw bytes).
+///
+/// The internal representation can be obtained using `.as_ref()`.
+///
+/// # Note
+///
+/// Methods from/to BER and DER encodings are also implemented for primitive types
+/// (`u8`, `u16` to `u128`, and `i8` to `i128`).
+/// In most cases, it is easier to use these types directly.
+///
+/// # Examples
+///
+/// Creating an `Integer`
+///
+/// ```
+/// use asn1_rs::Integer;
+///
+/// // unsigned
+/// let i = Integer::from(4);
+/// assert_eq!(i.as_ref(), &[4]);
+/// // signed
+/// let j = Integer::from(-2);
+/// assert_eq!(j.as_ref(), &[0xfe]);
+/// ```
+///
+/// Converting an `Integer` to a primitive type (using the `TryInto` trait)
+///
+/// ```
+/// use asn1_rs::{Error, Integer};
+/// use std::convert::TryInto;
+///
+/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]);
+/// // converts to an u32
+/// let n: u32 = i.try_into().unwrap();
+///
+/// // Same, but converting to an u16: will fail, value cannot fit into an u16
+/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]);
+/// assert_eq!(i.try_into() as Result<u16, _>, Err(Error::IntegerTooLarge));
+/// ```
+///
+/// Encoding an `Integer` to DER
+///
+/// ```
+/// use asn1_rs::{Integer, ToDer};
+///
+/// let i = Integer::from(4);
+/// let v = i.to_der_vec().unwrap();
+/// assert_eq!(&v, &[2, 1, 4]);
+///
+/// // same, with primitive types
+/// let v = 4.to_der_vec().unwrap();
+/// assert_eq!(&v, &[2, 1, 4]);
+/// ```
+#[derive(Debug, Eq, PartialEq)]
+pub struct Integer<'a> {
+    pub(crate) data: Cow<'a, [u8]>,
+}
+
+impl<'a> Integer<'a> {
+    /// Creates a new `Integer` containing the given value (borrowed).
+    #[inline]
+    pub const fn new(s: &'a [u8]) -> Self {
+        Integer {
+            data: Cow::Borrowed(s),
+        }
+    }
+
+    /// Creates a borrowed `Any` for this object
+    #[inline]
+    pub fn any(&'a self) -> Any<'a> {
+        Any::from_tag_and_data(Self::TAG, &self.data)
+    }
+
+    /// Returns a `BigInt` built from this `Integer` value.
+    #[cfg(feature = "bigint")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+    pub fn as_bigint(&self) -> BigInt {
+        BigInt::from_signed_bytes_be(&self.data)
+    }
+
+    /// Returns a `BigUint` built from this `Integer` value.
+    #[cfg(feature = "bigint")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+    pub fn as_biguint(&self) -> Result<BigUint> {
+        if is_highest_bit_set(&self.data) {
+            Err(Error::IntegerNegative)
+        } else {
+            Ok(BigUint::from_bytes_be(&self.data))
+        }
+    }
+
+    /// Build an `Integer` from a constant array of bytes representation of an integer.
+    pub fn from_const_array<const N: usize>(b: [u8; N]) -> Self {
+        // if high bit set -> add leading 0 to ensure unsigned
+        if is_highest_bit_set(&b) {
+            let mut bytes = vec![0];
+            bytes.extend_from_slice(&b);
+
+            Integer {
+                data: Cow::Owned(bytes),
+            }
+        }
+        // otherwise -> remove 0 unless next has high bit set
+        else {
+            let mut idx = 0;
+
+            while idx < b.len() - 1 {
+                if b[idx] == 0 && b[idx + 1] < 0x80 {
+                    idx += 1;
+                    continue;
+                }
+                break;
+            }
+
+            Integer {
+                data: Cow::Owned(b[idx..].to_vec()),
+            }
+        }
+    }
+
+    fn from_const_array_negative<const N: usize>(b: [u8; N]) -> Self {
+        let mut idx = 0;
+
+        // Skip leading FF unless next has high bit clear
+        while idx < b.len() - 1 {
+            if b[idx] == 0xFF && b[idx + 1] >= 0x80 {
+                idx += 1;
+                continue;
+            }
+            break;
+        }
+
+        if idx == b.len() {
+            Integer {
+                data: Cow::Borrowed(&[0]),
+            }
+        } else {
+            Integer {
+                data: Cow::Owned(b[idx..].to_vec()),
+            }
+        }
+    }
+}
+
+macro_rules! impl_from_to {
+    ($ty:ty, $sty:expr, $from:ident, $to:ident) => {
+        impl From<$ty> for Integer<'_> {
+            fn from(i: $ty) -> Self {
+                Self::$from(i)
+            }
+        }
+
+        impl TryFrom<Integer<'_>> for $ty {
+            type Error = Error;
+
+            fn try_from(value: Integer<'_>) -> Result<Self> {
+                value.$to()
+            }
+        }
+
+        impl Integer<'_> {
+            #[doc = "Attempts to convert an `Integer` to a `"]
+            #[doc = $sty]
+            #[doc = "`."]
+            #[doc = ""]
+            #[doc = "This function returns an `IntegerTooLarge` error if the integer will not fit into the output type."]
+            pub fn $to(&self) -> Result<$ty> {
+                self.any().try_into()
+            }
+        }
+    };
+    (IMPL SIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => {
+        impl_from_to!($ty, $sty, $from, $to);
+
+        impl Integer<'_> {
+            #[doc = "Converts a `"]
+            #[doc = $sty]
+            #[doc = "` to an `Integer`"]
+            #[doc = ""]
+            #[doc = "Note: this function allocates data."]
+            pub fn $from(i: $ty) -> Self {
+                let b = i.to_be_bytes();
+                if i >= 0 {
+                    Self::from_const_array(b)
+                } else {
+                    Self::from_const_array_negative(b)
+                }
+            }
+        }
+    };
+    (IMPL UNSIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => {
+        impl_from_to!($ty, $sty, $from, $to);
+
+        impl Integer<'_> {
+            #[doc = "Converts a `"]
+            #[doc = $sty]
+            #[doc = "` to an `Integer`"]
+            #[doc = ""]
+            #[doc = "Note: this function allocates data."]
+            pub fn $from(i: $ty) -> Self {
+                Self::from_const_array(i.to_be_bytes())
+            }
+        }
+    };
+    (SIGNED $ty:ty, $from:ident, $to:ident) => {
+        impl_from_to!(IMPL SIGNED $ty, stringify!($ty), $from, $to);
+    };
+    (UNSIGNED $ty:ty, $from:ident, $to:ident) => {
+        impl_from_to!(IMPL UNSIGNED $ty, stringify!($ty), $from, $to);
+    };
+}
+
+impl_from_to!(SIGNED i8, from_i8, as_i8);
+impl_from_to!(SIGNED i16, from_i16, as_i16);
+impl_from_to!(SIGNED i32, from_i32, as_i32);
+impl_from_to!(SIGNED i64, from_i64, as_i64);
+impl_from_to!(SIGNED i128, from_i128, as_i128);
+
+impl_from_to!(UNSIGNED u8, from_u8, as_u8);
+impl_from_to!(UNSIGNED u16, from_u16, as_u16);
+impl_from_to!(UNSIGNED u32, from_u32, as_u32);
+impl_from_to!(UNSIGNED u64, from_u64, as_u64);
+impl_from_to!(UNSIGNED u128, from_u128, as_u128);
+
+impl<'a> AsRef<[u8]> for Integer<'a> {
+    fn as_ref(&self) -> &[u8] {
+        &self.data
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Integer<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Integer<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Integer<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Integer<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+        Ok(Integer {
+            data: Cow::Borrowed(any.data),
+        })
+    }
+}
+
+impl<'a> CheckDerConstraints for Integer<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        check_der_int_constraints(any)
+    }
+}
+
+fn check_der_int_constraints(any: &Any) -> Result<()> {
+    any.header.assert_primitive()?;
+    any.header.length.assert_definite()?;
+    match any.as_bytes() {
+        [] => Err(Error::DerConstraintFailed(DerConstraint::IntegerEmpty)),
+        [0] => Ok(()),
+        // leading zeroes
+        [0, byte, ..] if *byte < 0x80 => Err(Error::DerConstraintFailed(
+            DerConstraint::IntegerLeadingZeroes,
+        )),
+        // negative integer with non-minimal encoding
+        [0xff, byte, ..] if *byte >= 0x80 => {
+            Err(Error::DerConstraintFailed(DerConstraint::IntegerLeadingFF))
+        }
+        _ => Ok(()),
+    }
+}
+
+impl DerAutoDerive for Integer<'_> {}
+
+impl<'a> Tagged for Integer<'a> {
+    const TAG: Tag = Tag::Integer;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Integer<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.data.len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // hmm, a very long integer. anyway:
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.data.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&self.data).map_err(Into::into)
+    }
+}
+
+/// Helper macro to declare integers at compile-time
+///
+/// [`Integer`] stores the encoded representation of the integer, so declaring
+/// an integer requires to either use a runtime function or provide the encoded value.
+/// This macro simplifies this task by encoding the value.
+/// It can be used the following ways:
+///
+/// - `int!(1234)`: Create a const expression for the corresponding `Integer<'static>`
+/// - `int!(raw 1234)`: Return the DER encoded form as a byte array (hex-encoded, big-endian
+///    representation from the integer, with leading zeroes removed).
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{int, Integer};
+///
+/// const INT0: Integer = int!(1234);
+/// ```
+#[macro_export]
+macro_rules! int {
+    (raw $item:expr) => {
+        $crate::exports::asn1_rs_impl::encode_int!($item)
+    };
+    (rel $item:expr) => {
+        $crate::exports::asn1_rs_impl::encode_int!(rel $item)
+    };
+    ($item:expr) => {
+        $crate::Integer::new(
+            &$crate::int!(raw $item),
+        )
+    };
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Any, FromDer, Header, Tag, ToDer};
+    use std::convert::TryInto;
+
+    // Vectors from Section 5.7 of:
+    // https://luca.ntop.org/Teaching/Appunti/asn1.html
+    pub(crate) const I0_BYTES: &[u8] = &[0x02, 0x01, 0x00];
+    pub(crate) const I127_BYTES: &[u8] = &[0x02, 0x01, 0x7F];
+    pub(crate) const I128_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0x80];
+    pub(crate) const I256_BYTES: &[u8] = &[0x02, 0x02, 0x01, 0x00];
+    pub(crate) const INEG128_BYTES: &[u8] = &[0x02, 0x01, 0x80];
+    pub(crate) const INEG129_BYTES: &[u8] = &[0x02, 0x02, 0xFF, 0x7F];
+
+    // Additional vectors
+    pub(crate) const I255_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0xFF];
+    pub(crate) const I32767_BYTES: &[u8] = &[0x02, 0x02, 0x7F, 0xFF];
+    pub(crate) const I65535_BYTES: &[u8] = &[0x02, 0x03, 0x00, 0xFF, 0xFF];
+    pub(crate) const INEG32768_BYTES: &[u8] = &[0x02, 0x02, 0x80, 0x00];
+
+    #[test]
+    fn decode_i8() {
+        assert_eq!(0, i8::from_der(I0_BYTES).unwrap().1);
+        assert_eq!(127, i8::from_der(I127_BYTES).unwrap().1);
+        assert_eq!(-128, i8::from_der(INEG128_BYTES).unwrap().1);
+    }
+
+    #[test]
+    fn encode_i8() {
+        assert_eq!(0i8.to_der_vec().unwrap(), I0_BYTES);
+        assert_eq!(127i8.to_der_vec().unwrap(), I127_BYTES);
+        assert_eq!((-128i8).to_der_vec().unwrap(), INEG128_BYTES);
+    }
+
+    #[test]
+    fn decode_i16() {
+        assert_eq!(0, i16::from_der(I0_BYTES).unwrap().1);
+        assert_eq!(127, i16::from_der(I127_BYTES).unwrap().1);
+        assert_eq!(128, i16::from_der(I128_BYTES).unwrap().1);
+        assert_eq!(255, i16::from_der(I255_BYTES).unwrap().1);
+        assert_eq!(256, i16::from_der(I256_BYTES).unwrap().1);
+        assert_eq!(32767, i16::from_der(I32767_BYTES).unwrap().1);
+        assert_eq!(-128, i16::from_der(INEG128_BYTES).unwrap().1);
+        assert_eq!(-129, i16::from_der(INEG129_BYTES).unwrap().1);
+        assert_eq!(-32768, i16::from_der(INEG32768_BYTES).unwrap().1);
+    }
+
+    #[test]
+    fn encode_i16() {
+        assert_eq!(0i16.to_der_vec().unwrap(), I0_BYTES);
+        assert_eq!(127i16.to_der_vec().unwrap(), I127_BYTES);
+        assert_eq!(128i16.to_der_vec().unwrap(), I128_BYTES);
+        assert_eq!(255i16.to_der_vec().unwrap(), I255_BYTES);
+        assert_eq!(256i16.to_der_vec().unwrap(), I256_BYTES);
+        assert_eq!(32767i16.to_der_vec().unwrap(), I32767_BYTES);
+        assert_eq!((-128i16).to_der_vec().unwrap(), INEG128_BYTES);
+        assert_eq!((-129i16).to_der_vec().unwrap(), INEG129_BYTES);
+        assert_eq!((-32768i16).to_der_vec().unwrap(), INEG32768_BYTES);
+    }
+
+    #[test]
+    fn decode_u8() {
+        assert_eq!(0, u8::from_der(I0_BYTES).unwrap().1);
+        assert_eq!(127, u8::from_der(I127_BYTES).unwrap().1);
+        assert_eq!(255, u8::from_der(I255_BYTES).unwrap().1);
+    }
+
+    #[test]
+    fn encode_u8() {
+        assert_eq!(0u8.to_der_vec().unwrap(), I0_BYTES);
+        assert_eq!(127u8.to_der_vec().unwrap(), I127_BYTES);
+        assert_eq!(255u8.to_der_vec().unwrap(), I255_BYTES);
+    }
+
+    #[test]
+    fn decode_u16() {
+        assert_eq!(0, u16::from_der(I0_BYTES).unwrap().1);
+        assert_eq!(127, u16::from_der(I127_BYTES).unwrap().1);
+        assert_eq!(255, u16::from_der(I255_BYTES).unwrap().1);
+        assert_eq!(256, u16::from_der(I256_BYTES).unwrap().1);
+        assert_eq!(32767, u16::from_der(I32767_BYTES).unwrap().1);
+        assert_eq!(65535, u16::from_der(I65535_BYTES).unwrap().1);
+    }
+
+    #[test]
+    fn encode_u16() {
+        assert_eq!(0u16.to_der_vec().unwrap(), I0_BYTES);
+        assert_eq!(127u16.to_der_vec().unwrap(), I127_BYTES);
+        assert_eq!(255u16.to_der_vec().unwrap(), I255_BYTES);
+        assert_eq!(256u16.to_der_vec().unwrap(), I256_BYTES);
+        assert_eq!(32767u16.to_der_vec().unwrap(), I32767_BYTES);
+        assert_eq!(65535u16.to_der_vec().unwrap(), I65535_BYTES);
+    }
+
+    /// Integers must be encoded with a minimum number of octets
+    #[test]
+    fn reject_non_canonical() {
+        assert!(i8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+        assert!(i16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+        assert!(u8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+        assert!(u16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+    }
+
+    #[test]
+    fn declare_int() {
+        let int = super::int!(1234);
+        assert_eq!(int.try_into(), Ok(1234));
+    }
+
+    #[test]
+    fn trim_slice() {
+        use super::trim_slice;
+        let h = Header::new_simple(Tag(0));
+        // no zero nor ff - nothing to remove
+        let input: &[u8] = &[0x7f, 0xff, 0x00, 0x02];
+        assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+        //
+        // 0x00
+        //
+        // empty - nothing to remove
+        let input: &[u8] = &[];
+        assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+        // one zero - nothing to remove
+        let input: &[u8] = &[0];
+        assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+        // all zeroes - keep only one
+        let input: &[u8] = &[0, 0, 0];
+        assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+        // some zeroes - keep only the non-zero part
+        let input: &[u8] = &[0, 0, 1];
+        assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+        //
+        // 0xff
+        //
+        // one ff - nothing to remove
+        let input: &[u8] = &[0xff];
+        assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+        // all ff - keep only one
+        let input: &[u8] = &[0xff, 0xff, 0xff];
+        assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+        // some ff - keep only the non-zero part
+        let input: &[u8] = &[0xff, 0xff, 1];
+        assert_eq!(Ok(&input[1..]), trim_slice(&Any::new(h.clone(), input)));
+        // some ff and a MSB 1 - keep only the non-zero part
+        let input: &[u8] = &[0xff, 0xff, 0x80, 1];
+        assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+    }
+}
diff --git a/src/asn1_types/mod.rs b/src/asn1_types/mod.rs
new file mode 100644
index 0000000..33e50d6
--- /dev/null
+++ b/src/asn1_types/mod.rs
@@ -0,0 +1,26 @@
+mod any;
+mod bitstring;
+mod boolean;
+mod choice;
+mod embedded_pdv;
+mod end_of_content;
+mod enumerated;
+mod generalizedtime;
+mod integer;
+mod null;
+mod object_descriptor;
+mod octetstring;
+mod oid;
+mod optional;
+mod real;
+mod sequence;
+mod set;
+mod strings;
+mod tagged;
+mod utctime;
+
+pub use {
+    any::*, bitstring::*, boolean::*, choice::*, embedded_pdv::*, end_of_content::*, enumerated::*,
+    generalizedtime::*, integer::*, null::*, object_descriptor::*, octetstring::*, oid::*,
+    optional::*, real::*, sequence::*, set::*, strings::*, tagged::*, utctime::*,
+};
diff --git a/src/asn1_types/null.rs b/src/asn1_types/null.rs
new file mode 100644
index 0000000..ba9f0f5
--- /dev/null
+++ b/src/asn1_types/null.rs
@@ -0,0 +1,99 @@
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `NULL` type
+#[derive(Debug, PartialEq, Eq)]
+pub struct Null {}
+
+impl Null {
+    pub const fn new() -> Self {
+        Null {}
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Null {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Null> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Null {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Null> {
+        any.tag().assert_eq(Self::TAG)?;
+        if !any.header.length.is_null() {
+            return Err(Error::InvalidLength);
+        }
+        Ok(Null {})
+    }
+}
+
+impl CheckDerConstraints for Null {
+    fn check_constraints(_any: &Any) -> Result<()> {
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Null {}
+
+impl Tagged for Null {
+    const TAG: Tag = Tag::Null;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Null {
+    fn to_der_len(&self) -> Result<usize> {
+        Ok(2)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&[0x05, 0x00]).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        Ok(0)
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for () {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_primitive()?;
+        if !any.header.length.is_null() {
+            return Err(Error::InvalidLength);
+        }
+        Ok(())
+    }
+}
+
+impl CheckDerConstraints for () {
+    fn check_constraints(_any: &Any) -> Result<()> {
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for () {}
+
+impl Tagged for () {
+    const TAG: Tag = Tag::Null;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for () {
+    fn to_der_len(&self) -> Result<usize> {
+        Ok(2)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&[0x05, 0x00]).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        Ok(0)
+    }
+}
diff --git a/src/asn1_types/object_descriptor.rs b/src/asn1_types/object_descriptor.rs
new file mode 100644
index 0000000..db78870
--- /dev/null
+++ b/src/asn1_types/object_descriptor.rs
@@ -0,0 +1,17 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+// X.680 section 44.3
+// ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString
+
+asn1_string!(ObjectDescriptor);
+
+impl<'a> TestValidCharset for ObjectDescriptor<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        if !i.iter().all(u8::is_ascii) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/octetstring.rs b/src/asn1_types/octetstring.rs
new file mode 100644
index 0000000..b4b71e5
--- /dev/null
+++ b/src/asn1_types/octetstring.rs
@@ -0,0 +1,157 @@
+use crate::*;
+use alloc::borrow::Cow;
+use core::convert::TryFrom;
+
+/// ASN.1 `OCTETSTRING` type
+#[derive(Debug, PartialEq, Eq)]
+pub struct OctetString<'a> {
+    data: Cow<'a, [u8]>,
+}
+
+impl<'a> OctetString<'a> {
+    pub const fn new(s: &'a [u8]) -> Self {
+        OctetString {
+            data: Cow::Borrowed(s),
+        }
+    }
+
+    /// Get the bytes representation of the *content*
+    pub fn as_cow(&'a self) -> &Cow<'a, [u8]> {
+        &self.data
+    }
+
+    /// Get the bytes representation of the *content*
+    pub fn into_cow(self) -> Cow<'a, [u8]> {
+        self.data
+    }
+}
+
+impl<'a> AsRef<[u8]> for OctetString<'a> {
+    fn as_ref(&self) -> &[u8] {
+        &self.data
+    }
+}
+
+impl<'a> From<&'a [u8]> for OctetString<'a> {
+    fn from(b: &'a [u8]) -> Self {
+        OctetString {
+            data: Cow::Borrowed(b),
+        }
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for OctetString<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<OctetString<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for OctetString<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<OctetString<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+        Ok(OctetString {
+            data: Cow::Borrowed(any.data),
+        })
+    }
+}
+
+impl<'a> CheckDerConstraints for OctetString<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 10.2
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for OctetString<'_> {}
+
+impl<'a> Tagged for OctetString<'a> {
+    const TAG: Tag = Tag::OctetString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for OctetString<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.data.len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.data.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&self.data).map_err(Into::into)
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for &'a [u8] {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<&'a [u8]> {
+        any.tag().assert_eq(Self::TAG)?;
+        let s = OctetString::try_from(any)?;
+        match s.data {
+            Cow::Borrowed(s) => Ok(s),
+            Cow::Owned(_) => Err(Error::LifetimeError),
+        }
+    }
+}
+
+impl<'a> CheckDerConstraints for &'a [u8] {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 10.2
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for &'_ [u8] {}
+
+impl<'a> Tagged for &'a [u8] {
+    const TAG: Tag = Tag::OctetString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for &'_ [u8] {
+    fn to_der_len(&self) -> Result<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.len()),
+        );
+        Ok(header.to_der_len()? + self.len())
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(self).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/oid.rs b/src/asn1_types/oid.rs
new file mode 100644
index 0000000..a6629b4
--- /dev/null
+++ b/src/asn1_types/oid.rs
@@ -0,0 +1,517 @@
+use crate::*;
+use alloc::borrow::Cow;
+#[cfg(not(feature = "std"))]
+use alloc::format;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+use core::{
+    convert::TryFrom, fmt, iter::FusedIterator, marker::PhantomData, ops::Shl, str::FromStr,
+};
+
+#[cfg(feature = "bigint")]
+use num_bigint::BigUint;
+use num_traits::Num;
+
+/// An error for OID parsing functions.
+#[derive(Debug)]
+pub enum OidParseError {
+    TooShort,
+    /// Signalizes that the first or second component is too large.
+    /// The first must be within the range 0 to 6 (inclusive).
+    /// The second component must be less than 40.
+    FirstComponentsTooLarge,
+    ParseIntError,
+}
+
+/// Object ID (OID) representation which can be relative or non-relative.
+/// An example for an OID in string representation is `"1.2.840.113549.1.1.5"`.
+///
+/// For non-relative OIDs restrictions apply to the first two components.
+///
+/// This library contains a procedural macro `oid` which can be used to
+/// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)`
+/// for relative oids. See the [module documentation](index.html) for more information.
+#[derive(Hash, PartialEq, Eq, Clone)]
+
+pub struct Oid<'a> {
+    asn1: Cow<'a, [u8]>,
+    relative: bool,
+}
+
+impl<'a> TryFrom<Any<'a>> for Oid<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Oid<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Self> {
+        // check that any.data.last().unwrap() >> 7 == 0u8
+        let asn1 = Cow::Borrowed(any.data);
+        Ok(Oid::new(asn1))
+    }
+}
+
+impl<'a> CheckDerConstraints for Oid<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        any.header.length.assert_definite()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Oid<'_> {}
+
+impl<'a> Tagged for Oid<'a> {
+    const TAG: Tag = Tag::Oid;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Oid<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        // OID/REL-OID tag will not change header size, so we don't care here
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.asn1.len()),
+        );
+        Ok(header.to_der_len()? + self.asn1.len())
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let tag = if self.relative {
+            Tag::RelativeOid
+        } else {
+            Tag::Oid
+        };
+        let header = Header::new(
+            Class::Universal,
+            false,
+            tag,
+            Length::Definite(self.asn1.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&self.asn1).map_err(Into::into)
+    }
+}
+
+fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ {
+    ids.iter().flat_map(|id| {
+        let bit_count = 64 - id.leading_zeros();
+        let octets_needed = ((bit_count + 6) / 7).max(1);
+        (0..octets_needed).map(move |i| {
+            let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 };
+            ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag
+        })
+    })
+}
+
+impl<'a> Oid<'a> {
+    /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
+    /// for other ways to create oids.
+    pub const fn new(asn1: Cow<'a, [u8]>) -> Oid {
+        Oid {
+            asn1,
+            relative: false,
+        }
+    }
+
+    /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
+    /// for other ways to create relative oids.
+    pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid {
+        Oid {
+            asn1,
+            relative: true,
+        }
+    }
+
+    /// Build an OID from an array of object identifier components.
+    /// This method allocates memory on the heap.
+    pub fn from(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
+        if s.len() < 2 {
+            if s.len() == 1 && s[0] == 0 {
+                return Ok(Oid {
+                    asn1: Cow::Borrowed(&[0]),
+                    relative: false,
+                });
+            }
+            return Err(OidParseError::TooShort);
+        }
+        if s[0] >= 7 || s[1] >= 40 {
+            return Err(OidParseError::FirstComponentsTooLarge);
+        }
+        let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8]
+            .iter()
+            .copied()
+            .chain(encode_relative(&s[2..]))
+            .collect();
+        Ok(Oid {
+            asn1: Cow::from(asn1_encoded),
+            relative: false,
+        })
+    }
+
+    /// Build a relative OID from an array of object identifier components.
+    pub fn from_relative(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
+        if s.is_empty() {
+            return Err(OidParseError::TooShort);
+        }
+        let asn1_encoded: Vec<u8> = encode_relative(s).collect();
+        Ok(Oid {
+            asn1: Cow::from(asn1_encoded),
+            relative: true,
+        })
+    }
+
+    /// Create a deep copy of the oid.
+    ///
+    /// This method allocates data on the heap. The returned oid
+    /// can be used without keeping the ASN.1 representation around.
+    ///
+    /// Cloning the returned oid does again allocate data.
+    pub fn to_owned(&self) -> Oid<'static> {
+        Oid {
+            asn1: Cow::from(self.asn1.to_vec()),
+            relative: self.relative,
+        }
+    }
+
+    /// Get the encoded oid without the header.
+    #[inline]
+    pub fn as_bytes(&self) -> &[u8] {
+        self.asn1.as_ref()
+    }
+
+    /// Get the encoded oid without the header.
+    #[deprecated(since = "0.2.0", note = "Use `as_bytes` instead")]
+    #[inline]
+    pub fn bytes(&self) -> &[u8] {
+        self.as_bytes()
+    }
+
+    /// Get the bytes representation of the encoded oid
+    pub fn into_cow(self) -> Cow<'a, [u8]> {
+        self.asn1
+    }
+
+    /// Convert the OID to a string representation.
+    /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5"
+    #[cfg(feature = "bigint")]
+    pub fn to_id_string(&self) -> String {
+        let ints: Vec<String> = self.iter_bigint().map(|i| i.to_string()).collect();
+        ints.join(".")
+    }
+
+    #[cfg(not(feature = "bigint"))]
+    /// Convert the OID to a string representation.
+    ///
+    /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5"
+    /// is returned, otherwise a hex representation.
+    ///
+    /// See also the "bigint" feature of this crate.
+    pub fn to_id_string(&self) -> String {
+        if let Some(arcs) = self.iter() {
+            let ints: Vec<String> = arcs.map(|i| i.to_string()).collect();
+            ints.join(".")
+        } else {
+            let mut ret = String::with_capacity(self.asn1.len() * 3);
+            for (i, o) in self.asn1.iter().enumerate() {
+                ret.push_str(&format!("{:02x}", o));
+                if i + 1 != self.asn1.len() {
+                    ret.push(' ');
+                }
+            }
+            ret
+        }
+    }
+
+    /// Return an iterator over the sub-identifiers (arcs).
+    #[cfg(feature = "bigint")]
+    pub fn iter_bigint(
+        &'_ self,
+    ) -> impl Iterator<Item = BigUint> + FusedIterator + ExactSizeIterator + '_ {
+        SubIdentifierIterator {
+            oid: self,
+            pos: 0,
+            first: false,
+            n: PhantomData,
+        }
+    }
+
+    /// Return an iterator over the sub-identifiers (arcs).
+    /// Returns `None` if at least one arc does not fit into `u64`.
+    pub fn iter(
+        &'_ self,
+    ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_> {
+        // Check that every arc fits into u64
+        let bytes = if self.relative {
+            &self.asn1
+        } else if self.asn1.is_empty() {
+            &[]
+        } else {
+            &self.asn1[1..]
+        };
+        let max_bits = bytes
+            .iter()
+            .fold((0usize, 0usize), |(max, cur), c| {
+                let is_end = (c >> 7) == 0u8;
+                if is_end {
+                    (max.max(cur + 7), 0)
+                } else {
+                    (max, cur + 7)
+                }
+            })
+            .0;
+        if max_bits > 64 {
+            return None;
+        }
+
+        Some(SubIdentifierIterator {
+            oid: self,
+            pos: 0,
+            first: false,
+            n: PhantomData,
+        })
+    }
+
+    pub fn from_ber_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, any) = Any::from_ber(bytes)?;
+        any.header.assert_primitive()?;
+        any.header.assert_tag(Tag::RelativeOid)?;
+        let asn1 = Cow::Borrowed(any.data);
+        Ok((rem, Oid::new_relative(asn1)))
+    }
+
+    pub fn from_der_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, any) = Any::from_der(bytes)?;
+        any.header.assert_tag(Tag::RelativeOid)?;
+        Self::check_constraints(&any)?;
+        let asn1 = Cow::Borrowed(any.data);
+        Ok((rem, Oid::new_relative(asn1)))
+    }
+
+    /// Returns true if `needle` is a prefix of the OID.
+    pub fn starts_with(&self, needle: &Oid) -> bool {
+        self.asn1.len() >= needle.asn1.len() && self.asn1.starts_with(needle.as_bytes())
+    }
+}
+
+trait Repr: Num + Shl<usize, Output = Self> + From<u8> {}
+impl<N> Repr for N where N: Num + Shl<usize, Output = N> + From<u8> {}
+
+struct SubIdentifierIterator<'a, N: Repr> {
+    oid: &'a Oid<'a>,
+    pos: usize,
+    first: bool,
+    n: PhantomData<&'a N>,
+}
+
+impl<'a, N: Repr> Iterator for SubIdentifierIterator<'a, N> {
+    type Item = N;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        use num_traits::identities::Zero;
+
+        if self.pos == self.oid.asn1.len() {
+            return None;
+        }
+        if !self.oid.relative {
+            if !self.first {
+                debug_assert!(self.pos == 0);
+                self.first = true;
+                return Some((self.oid.asn1[0] / 40).into());
+            } else if self.pos == 0 {
+                self.pos += 1;
+                if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 {
+                    return None;
+                }
+                return Some((self.oid.asn1[0] % 40).into());
+            }
+        }
+        // decode objet sub-identifier according to the asn.1 standard
+        let mut res = <N as Zero>::zero();
+        for o in self.oid.asn1[self.pos..].iter() {
+            self.pos += 1;
+            res = (res << 7) + (o & 0b111_1111).into();
+            let flag = o >> 7;
+            if flag == 0u8 {
+                break;
+            }
+        }
+        Some(res)
+    }
+}
+
+impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {}
+
+impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> {
+    fn len(&self) -> usize {
+        if self.oid.relative {
+            self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count()
+        } else if self.oid.asn1.len() == 0 {
+            0
+        } else if self.oid.asn1.len() == 1 {
+            if self.oid.asn1[0] == 0 {
+                1
+            } else {
+                2
+            }
+        } else {
+            2 + self.oid.asn1[2..]
+                .iter()
+                .filter(|o| (*o >> 7) == 0u8)
+                .count()
+        }
+    }
+
+    #[cfg(feature = "exact_size_is_empty")]
+    fn is_empty(&self) -> bool {
+        self.oid.asn1.is_empty()
+    }
+}
+
+impl<'a> fmt::Display for Oid<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if self.relative {
+            f.write_str("rel. ")?;
+        }
+        f.write_str(&self.to_id_string())
+    }
+}
+
+impl<'a> fmt::Debug for Oid<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str("OID(")?;
+        <Oid as fmt::Display>::fmt(self, f)?;
+        f.write_str(")")
+    }
+}
+
+impl<'a> FromStr for Oid<'a> {
+    type Err = OidParseError;
+
+    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
+        let v: core::result::Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect();
+        v.map_err(|_| OidParseError::ParseIntError)
+            .and_then(|v| Oid::from(&v))
+    }
+}
+
+/// Helper macro to declare integers at compile-time
+///
+/// Since the DER encoded oids are not very readable we provide a
+/// procedural macro `oid!`. The macro can be used the following ways:
+///
+/// - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>`
+/// - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>`
+/// - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array.
+///
+/// # Comparing oids
+///
+/// Comparing a parsed oid to a static oid is probably the most common
+/// thing done with oids in your code. The `oid!` macro can be used in expression positions for
+/// this purpose. For example
+/// ```
+/// use asn1_rs::{oid, Oid};
+///
+/// # let some_oid: Oid<'static> = oid!(1.2.456);
+/// const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456);
+/// assert_eq!(some_oid, SOME_STATIC_OID)
+/// ```
+/// To get a relative Oid use `oid!(rel 1.2)`.
+///
+/// Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727))
+/// and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434))
+/// the `oid` macro can not directly be used in patterns, also not through constants.
+/// You can do this, though:
+/// ```
+/// # use asn1_rs::{oid, Oid};
+/// # let some_oid: Oid<'static> = oid!(1.2.456);
+/// const SOME_OID: Oid<'static> = oid!(1.2.456);
+/// if some_oid == SOME_OID || some_oid == oid!(1.2.456) {
+///     println!("match");
+/// }
+///
+/// // Alternatively, compare the DER encoded form directly:
+/// const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456);
+/// match some_oid.as_bytes() {
+///     SOME_OID_RAW => println!("match"),
+///     _ => panic!("no match"),
+/// }
+/// ```
+/// *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An
+/// extra check might be necessary.
+#[macro_export]
+macro_rules! oid {
+    (raw $items:expr) => {
+        $crate::exports::asn1_rs_impl::encode_oid!($items)
+    };
+    (rel $items:expr) => {
+        $crate::Oid::new_relative($crate::exports::borrow::Cow::Borrowed(
+            &$crate::exports::asn1_rs_impl::encode_oid!(rel $items),
+        ))
+    };
+    ($items:expr) => {
+        $crate::Oid::new($crate::exports::borrow::Cow::Borrowed(
+            &$crate::oid!(raw $items),
+        ))
+    };
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{FromDer, Oid, ToDer};
+    use hex_literal::hex;
+
+    #[test]
+    fn declare_oid() {
+        let oid = super::oid! {1.2.840.113549.1};
+        assert_eq!(oid.to_string(), "1.2.840.113549.1");
+    }
+
+    const OID_RSA_ENCRYPTION: &[u8] = &oid! {raw 1.2.840.113549.1.1.1};
+    const OID_EC_PUBLIC_KEY: &[u8] = &oid! {raw 1.2.840.10045.2.1};
+    #[allow(clippy::match_like_matches_macro)]
+    fn compare_oid(oid: &Oid) -> bool {
+        match oid.as_bytes() {
+            OID_RSA_ENCRYPTION => true,
+            OID_EC_PUBLIC_KEY => true,
+            _ => false,
+        }
+    }
+
+    #[test]
+    fn test_compare_oid() {
+        let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
+        assert_eq!(oid, oid! {1.2.840.113549.1.1.1});
+        let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
+        assert!(compare_oid(&oid));
+    }
+
+    #[test]
+    fn oid_to_der() {
+        let oid = super::oid! {1.2.840.113549.1};
+        assert_eq!(oid.to_der_len(), Ok(9));
+        let v = oid.to_der_vec().expect("could not serialize");
+        assert_eq!(&v, &hex! {"06 07 2a 86 48 86 f7 0d 01"});
+        let (_, oid2) = Oid::from_der(&v).expect("could not re-parse");
+        assert_eq!(&oid, &oid2);
+    }
+
+    #[test]
+    fn oid_starts_with() {
+        const OID_RSA_ENCRYPTION: Oid = oid! {1.2.840.113549.1.1.1};
+        const OID_EC_PUBLIC_KEY: Oid = oid! {1.2.840.10045.2.1};
+        let oid = super::oid! {1.2.840.113549.1};
+        assert!(OID_RSA_ENCRYPTION.starts_with(&oid));
+        assert!(!OID_EC_PUBLIC_KEY.starts_with(&oid));
+    }
+}
diff --git a/src/asn1_types/optional.rs b/src/asn1_types/optional.rs
new file mode 100644
index 0000000..a8027ab
--- /dev/null
+++ b/src/asn1_types/optional.rs
@@ -0,0 +1,87 @@
+use crate::*;
+
+// note: we cannot implement `TryFrom<Any<'a>> with generic errors for Option<T>`,
+// because this conflicts with generic `T` implementation in
+// `src/traits.rs`, since `T` always satisfies `T: Into<Option<T>>`
+//
+// for the same reason, we cannot use a generic error type here
+impl<'a, T> FromBer<'a> for Option<T>
+where
+    T: FromBer<'a>,
+{
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+        if bytes.is_empty() {
+            return Ok((bytes, None));
+        }
+        match T::from_ber(bytes) {
+            Ok((rem, t)) => Ok((rem, Some(t))),
+            Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+impl<'a, T> FromDer<'a> for Option<T>
+where
+    T: FromDer<'a>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
+        if bytes.is_empty() {
+            return Ok((bytes, None));
+        }
+        match T::from_der(bytes) {
+            Ok((rem, t)) => Ok((rem, Some(t))),
+            Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+impl<T> CheckDerConstraints for Option<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        T::check_constraints(any)
+    }
+}
+
+impl<T> DynTagged for Option<T>
+where
+    T: DynTagged,
+{
+    fn tag(&self) -> Tag {
+        if self.is_some() {
+            self.tag()
+        } else {
+            Tag(0)
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for Option<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        match self {
+            None => Ok(0),
+            Some(t) => t.to_der_len(),
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        match self {
+            None => Ok(0),
+            Some(t) => t.write_der_header(writer),
+        }
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        match self {
+            None => Ok(0),
+            Some(t) => t.write_der_content(writer),
+        }
+    }
+}
diff --git a/src/asn1_types/real.rs b/src/asn1_types/real.rs
new file mode 100644
index 0000000..db72a0d
--- /dev/null
+++ b/src/asn1_types/real.rs
@@ -0,0 +1,462 @@
+use crate::*;
+use alloc::format;
+use core::convert::TryFrom;
+use nom::Needed;
+
+mod f32;
+mod f64;
+pub use self::f32::*;
+pub use self::f64::*;
+
+/// ASN.1 `REAL` type
+///
+/// # Limitations
+///
+/// When encoding binary values, only base 2 is supported
+#[derive(Debug, PartialEq)]
+pub enum Real {
+    /// Non-special values
+    Binary {
+        mantissa: f64,
+        base: u32,
+        exponent: i32,
+        enc_base: u8,
+    },
+    /// Infinity (∞).
+    Infinity,
+    /// Negative infinity (−∞).
+    NegInfinity,
+    /// Zero
+    Zero,
+}
+
+impl Real {
+    /// Create a new `REAL` from the `f64` value.
+    pub fn new(f: f64) -> Self {
+        if f.is_infinite() {
+            if f.is_sign_positive() {
+                Self::Infinity
+            } else {
+                Self::NegInfinity
+            }
+        } else if f.abs() == 0.0 {
+            Self::Zero
+        } else {
+            let mut e = 0;
+            let mut f = f;
+            while f.fract() != 0.0 {
+                f *= 10.0_f64;
+                e -= 1;
+            }
+            Real::Binary {
+                mantissa: f,
+                base: 10,
+                exponent: e,
+                enc_base: 10,
+            }
+            .normalize_base10()
+        }
+    }
+
+    pub const fn with_enc_base(self, enc_base: u8) -> Self {
+        match self {
+            Real::Binary {
+                mantissa,
+                base,
+                exponent,
+                ..
+            } => Real::Binary {
+                mantissa,
+                base,
+                exponent,
+                enc_base,
+            },
+            e => e,
+        }
+    }
+
+    fn normalize_base10(self) -> Self {
+        match self {
+            Real::Binary {
+                mantissa,
+                base: 10,
+                exponent,
+                enc_base: _enc_base,
+            } => {
+                let mut m = mantissa;
+                let mut e = exponent;
+                while m.abs() > f64::EPSILON && m.rem_euclid(10.0).abs() < f64::EPSILON {
+                    m /= 10.0;
+                    e += 1;
+                }
+                Real::Binary {
+                    mantissa: m,
+                    base: 10,
+                    exponent: e,
+                    enc_base: _enc_base,
+                }
+            }
+            _ => self,
+        }
+    }
+
+    /// Create a new binary `REAL`
+    #[inline]
+    pub const fn binary(mantissa: f64, base: u32, exponent: i32) -> Self {
+        Self::Binary {
+            mantissa,
+            base,
+            exponent,
+            enc_base: 2,
+        }
+    }
+
+    /// Returns `true` if this value is positive infinity or negative infinity, and
+    /// `false` otherwise.
+    #[inline]
+    pub fn is_infinite(&self) -> bool {
+        matches!(self, Real::Infinity | Real::NegInfinity)
+    }
+
+    /// Returns `true` if this number is not infinite.
+    #[inline]
+    pub fn is_finite(&self) -> bool {
+        matches!(self, Real::Zero | Real::Binary { .. })
+    }
+
+    /// Returns the 'f64' value of this `REAL`.
+    ///
+    /// Returned value is a float, and may be infinite.
+    pub fn f64(&self) -> f64 {
+        match self {
+            Real::Binary {
+                mantissa,
+                base,
+                exponent,
+                ..
+            } => {
+                let f = mantissa;
+                let exp = (*base as f64).powi(*exponent);
+                f * exp
+            }
+            Real::Zero => 0.0_f64,
+            Real::Infinity => f64::INFINITY,
+            Real::NegInfinity => f64::NEG_INFINITY,
+        }
+    }
+
+    /// Returns the 'f32' value of this `REAL`.
+    ///
+    /// This functions casts the result of [`Real::f64`] to a `f32`, and loses precision.
+    pub fn f32(&self) -> f32 {
+        self.f64() as f32
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Real {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_primitive()?;
+        let data = &any.data;
+        if data.is_empty() {
+            return Ok(Real::Zero);
+        }
+        // code inspired from pyasn1
+        let first = data[0];
+        let rem = &data[1..];
+        if first & 0x80 != 0 {
+            // binary encoding (X.690 section 8.5.6)
+            let rem = rem;
+            // format of exponent
+            let (n, rem) = match first & 0x03 {
+                4 => {
+                    let (b, rem) = rem
+                        .split_first()
+                        .ok_or_else(|| Error::Incomplete(Needed::new(1)))?;
+                    (*b as usize, rem)
+                }
+                b => (b as usize + 1, rem),
+            };
+            if n >= rem.len() {
+                return Err(any.tag().invalid_value("Invalid float value(exponent)"));
+            }
+            // n cannot be 0 (see the +1 above)
+            let (eo, rem) = rem.split_at(n);
+            // so 'eo' cannot be empty
+            let mut e = if eo[0] & 0x80 != 0 { -1 } else { 0 };
+            // safety check: 'eo' length must be <= container type for 'e'
+            if eo.len() > 4 {
+                return Err(any.tag().invalid_value("Exponent too large (REAL)"));
+            }
+            for b in eo {
+                e = (e << 8) | (*b as i32);
+            }
+            // base bits
+            let b = (first >> 4) & 0x03;
+            let _enc_base = match b {
+                0 => 2,
+                1 => 8,
+                2 => 16,
+                _ => return Err(any.tag().invalid_value("Illegal REAL encoding base")),
+            };
+            let e = match b {
+                // base 2
+                0 => e,
+                // base 8
+                1 => e * 3,
+                // base 16
+                2 => e * 4,
+                _ => return Err(any.tag().invalid_value("Illegal REAL base")),
+            };
+            if rem.len() > 8 {
+                return Err(any.tag().invalid_value("Mantissa too large (REAL)"));
+            }
+            let mut p = 0;
+            for b in rem {
+                p = (p << 8) | (*b as i64);
+            }
+            // sign bit
+            let p = if first & 0x40 != 0 { -p } else { p };
+            // scale bits
+            let sf = (first >> 2) & 0x03;
+            let p = match sf {
+                0 => p as f64,
+                sf => {
+                    // 2^sf: cannot overflow, sf is between 0 and 3
+                    let scale = 2_f64.powi(sf as _);
+                    (p as f64) * scale
+                }
+            };
+            Ok(Real::Binary {
+                mantissa: p,
+                base: 2,
+                exponent: e,
+                enc_base: _enc_base,
+            })
+        } else if first & 0x40 != 0 {
+            // special real value (X.690 section 8.5.8)
+            // there shall be only one contents octet,
+            if any.header.length != Length::Definite(1) {
+                return Err(Error::InvalidLength);
+            }
+            // with values as follows
+            match first {
+                0x40 => Ok(Real::Infinity),
+                0x41 => Ok(Real::NegInfinity),
+                _ => Err(any.tag().invalid_value("Invalid float special value")),
+            }
+        } else {
+            // decimal encoding (X.690 section 8.5.7)
+            let s = alloc::str::from_utf8(rem)?;
+            match first & 0x03 {
+                0x1 => {
+                    // NR1
+                    match s.parse::<u32>() {
+                        Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")),
+                        Ok(v) => Ok(Real::new(v.into())),
+                    }
+                }
+                0x2 /* NR2 */ | 0x3 /* NR3 */=> {
+                    match s.parse::<f64>() {
+                        Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")),
+                        Ok(v) => Ok(Real::new(v)),
+                    }
+                        }
+                c => {
+                    Err(any.tag().invalid_value(&format!("Invalid NR ({})", c)))
+                }
+            }
+        }
+    }
+}
+
+impl CheckDerConstraints for Real {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        any.header.length.assert_definite()?;
+        // XXX more checks
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for Real {}
+
+impl Tagged for Real {
+    const TAG: Tag = Tag::RealType;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Real {
+    fn to_der_len(&self) -> Result<usize> {
+        match self {
+            Real::Zero => Ok(0),
+            Real::Infinity | Real::NegInfinity => Ok(1),
+            Real::Binary { .. } => {
+                let mut sink = std::io::sink();
+                let n = self
+                    .write_der_content(&mut sink)
+                    .map_err(|_| Self::TAG.invalid_value("Serialization of REAL failed"))?;
+                Ok(n)
+            }
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.to_der_len()?),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        match self {
+            Real::Zero => Ok(0),
+            Real::Infinity => writer.write(&[0x40]).map_err(Into::into),
+            Real::NegInfinity => writer.write(&[0x41]).map_err(Into::into),
+            Real::Binary {
+                mantissa,
+                base,
+                exponent,
+                enc_base: _enc_base,
+            } => {
+                if *base == 10 {
+                    // using character form
+                    let sign = if *exponent == 0 { "+" } else { "" };
+                    let s = format!("\x03{}E{}{}", mantissa, sign, exponent);
+                    return writer.write(s.as_bytes()).map_err(Into::into);
+                }
+                if *base != 2 {
+                    return Err(Self::TAG.invalid_value("Invalid base for REAL").into());
+                }
+                let mut first: u8 = 0x80;
+                // choose encoding base
+                let enc_base = *_enc_base;
+                let (ms, mut m, enc_base, mut e) =
+                    drop_floating_point(*mantissa, enc_base, *exponent);
+                assert!(m != 0);
+                if ms < 0 {
+                    first |= 0x40
+                };
+                // exponent & mantissa normalization
+                match enc_base {
+                    2 => {
+                        while m & 0x1 == 0 {
+                            m >>= 1;
+                            e += 1;
+                        }
+                    }
+                    8 => {
+                        while m & 0x7 == 0 {
+                            m >>= 3;
+                            e += 1;
+                        }
+                        first |= 0x10;
+                    }
+                    _ /* 16 */ => {
+                        while m & 0xf == 0 {
+                            m >>= 4;
+                            e += 1;
+                        }
+                        first |= 0x20;
+                    }
+                }
+                // scale factor
+                // XXX in DER, sf is always 0 (11.3.1)
+                let mut sf = 0;
+                while m & 0x1 == 0 && sf < 4 {
+                    m >>= 1;
+                    sf += 1;
+                }
+                first |= sf << 2;
+                // exponent length and bytes
+                let len_e = match e.abs() {
+                    0..=0xff => 1,
+                    0x100..=0xffff => 2,
+                    0x1_0000..=0xff_ffff => 3,
+                    // e is an `i32` so it can't be longer than 4 bytes
+                    // use 4, so `first` is ORed with 3
+                    _ => 4,
+                };
+                first |= (len_e - 1) & 0x3;
+                // write first byte
+                let mut n = writer.write(&[first])?;
+                // write exponent
+                // special case: number of bytes from exponent is > 3 and cannot fit in 2 bits
+                #[allow(clippy::identity_op)]
+                if len_e == 4 {
+                    let b = len_e & 0xff;
+                    n += writer.write(&[b])?;
+                }
+                // we only need to write e.len() bytes
+                let bytes = e.to_be_bytes();
+                n += writer.write(&bytes[(4 - len_e) as usize..])?;
+                // write mantissa
+                let bytes = m.to_be_bytes();
+                let mut idx = 0;
+                for &b in bytes.iter() {
+                    if b != 0 {
+                        break;
+                    }
+                    idx += 1;
+                }
+                n += writer.write(&bytes[idx..])?;
+                Ok(n)
+            }
+        }
+    }
+}
+
+impl From<f32> for Real {
+    fn from(f: f32) -> Self {
+        Real::new(f.into())
+    }
+}
+
+impl From<f64> for Real {
+    fn from(f: f64) -> Self {
+        Real::new(f)
+    }
+}
+
+impl From<Real> for f32 {
+    fn from(r: Real) -> Self {
+        r.f32()
+    }
+}
+
+impl From<Real> for f64 {
+    fn from(r: Real) -> Self {
+        r.f64()
+    }
+}
+
+#[cfg(feature = "std")]
+fn drop_floating_point(m: f64, b: u8, e: i32) -> (i8, u64, u8, i32) {
+    let ms = if m.is_sign_positive() { 1 } else { -1 };
+    let es = if e.is_positive() { 1 } else { -1 };
+    let mut m = m.abs();
+    let mut e = e;
+    //
+    if b == 8 {
+        m *= 2_f64.powi((e.abs() / 3) * es);
+        e = (e.abs() / 3) * es;
+    } else if b == 16 {
+        m *= 2_f64.powi((e.abs() / 4) * es);
+        e = (e.abs() / 4) * es;
+    }
+    //
+    while m.abs() > f64::EPSILON {
+        if m.fract() != 0.0 {
+            m *= b as f64;
+            e -= 1;
+        } else {
+            break;
+        }
+    }
+    (ms, m as u64, b, e)
+}
diff --git a/src/asn1_types/real/f32.rs b/src/asn1_types/real/f32.rs
new file mode 100644
index 0000000..cb41dbf
--- /dev/null
+++ b/src/asn1_types/real/f32.rs
@@ -0,0 +1,27 @@
+use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged};
+use core::convert::{TryFrom, TryInto};
+
+impl<'a> TryFrom<Any<'a>> for f32 {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<f32> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_primitive()?;
+        let real: Real = any.try_into()?;
+        Ok(real.f32())
+    }
+}
+
+impl CheckDerConstraints for f32 {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        any.header.length.assert_definite()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for f32 {}
+
+impl Tagged for f32 {
+    const TAG: Tag = Tag::RealType;
+}
diff --git a/src/asn1_types/real/f64.rs b/src/asn1_types/real/f64.rs
new file mode 100644
index 0000000..4986d9b
--- /dev/null
+++ b/src/asn1_types/real/f64.rs
@@ -0,0 +1,27 @@
+use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged};
+use core::convert::{TryFrom, TryInto};
+
+impl<'a> TryFrom<Any<'a>> for f64 {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<f64> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_primitive()?;
+        let real: Real = any.try_into()?;
+        Ok(real.f64())
+    }
+}
+
+impl CheckDerConstraints for f64 {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        any.header.length.assert_definite()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for f64 {}
+
+impl Tagged for f64 {
+    const TAG: Tag = Tag::RealType;
+}
diff --git a/src/asn1_types/sequence.rs b/src/asn1_types/sequence.rs
new file mode 100644
index 0000000..3a65ec0
--- /dev/null
+++ b/src/asn1_types/sequence.rs
@@ -0,0 +1,398 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+mod iterator;
+mod sequence_of;
+mod vec;
+
+pub use iterator::*;
+pub use sequence_of::*;
+pub use vec::*;
+
+/// The `SEQUENCE` object is an ordered list of heteregeneous types.
+///
+/// Sequences can usually be of 2 types:
+/// - a list of different objects (`SEQUENCE`, usually parsed as a `struct`)
+/// - a list of similar objects (`SEQUENCE OF`, usually parsed as a `Vec<T>`)
+///
+/// The current object covers the former. For the latter, see the [`SequenceOf`] documentation.
+///
+/// The `Sequence` object contains the (*unparsed*) encoded representation of its content. It provides
+/// methods to parse and iterate contained objects, or convert the sequence to other types.
+///
+/// # Building a Sequence
+///
+/// To build a DER sequence:
+/// - if the sequence is composed of objects of the same type, the [`Sequence::from_iter_to_der`] method can be used
+/// - otherwise, the [`ToDer`] trait can be used to create content incrementally
+///
+/// ```
+/// use asn1_rs::{Integer, Sequence, SerializeResult, ToDer};
+///
+/// fn build_seq<'a>() -> SerializeResult<Sequence<'a>> {
+///     let mut v = Vec::new();
+///     // add an Integer object (construct type):
+///     let i = Integer::from_u32(4);
+///     let _ = i.write_der(&mut v)?;
+///     // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`:
+///     let _ = "abcd".write_der(&mut v)?;
+///     // return the sequence built from the DER content
+///     Ok(Sequence::new(v.into()))
+/// }
+///
+/// let seq = build_seq().unwrap();
+///
+/// ```
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Error, Sequence};
+///
+/// // build sequence
+/// let it = [2, 3, 4].iter();
+/// let seq = Sequence::from_iter_to_der(it).unwrap();
+///
+/// // `seq` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in seq.der_iter::<u32, Error>() {
+///     // item has type `Result<u32>`, since parsing the serialized bytes could fail
+///     sum += item.expect("parsing list item failed");
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+///
+/// Note: the above example encodes a `SEQUENCE OF INTEGER` object, the [`SequenceOf`] object could
+/// be used to provide a simpler API.
+///
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Sequence<'a> {
+    /// Serialized DER representation of the sequence content
+    pub content: Cow<'a, [u8]>,
+}
+
+impl<'a> Sequence<'a> {
+    /// Build a sequence, given the provided content
+    pub const fn new(content: Cow<'a, [u8]>) -> Self {
+        Sequence { content }
+    }
+
+    /// Consume the sequence and return the content
+    #[inline]
+    pub fn into_content(self) -> Cow<'a, [u8]> {
+        self.content
+    }
+
+    /// Apply the parsing function to the sequence content, consuming the sequence
+    ///
+    /// Note: this function expects the caller to take ownership of content.
+    /// In some cases, handling the lifetime of objects is not easy (when keeping only references on
+    /// data). Other methods are provided (depending on the use case):
+    /// - [`Sequence::parse`] takes a reference on the sequence data, but does not consume it,
+    /// - [`Sequence::from_der_and_then`] does the parsing of the sequence and applying the function
+    ///   in one step, ensuring there are only references (and dropping the temporary sequence).
+    pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>,
+    {
+        op(self.content)
+    }
+
+    /// Same as [`Sequence::from_der_and_then`], but using BER encoding (no constraints).
+    pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+        E: From<Error>,
+    {
+        let (rem, seq) = Sequence::from_ber(bytes).map_err(Err::convert)?;
+        let data = match seq.content {
+            Cow::Borrowed(b) => b,
+            // Since 'any' is built from 'bytes', it is borrowed by construction
+            Cow::Owned(_) => unreachable!(),
+        };
+        let (_, res) = op(data)?;
+        Ok((rem, res))
+    }
+
+    /// Parse a DER sequence and apply the provided parsing function to content
+    ///
+    /// After parsing, the sequence object and header are discarded.
+    ///
+    /// ```
+    /// use asn1_rs::{FromDer, ParseResult, Sequence};
+    ///
+    /// // Parse a SEQUENCE {
+    /// //      a INTEGER (0..255),
+    /// //      b INTEGER (0..4294967296)
+    /// // }
+    /// // and return only `(a,b)
+    /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> {
+    ///     Sequence::from_der_and_then(i, |i| {
+    ///             let (i, a) = u8::from_der(i)?;
+    ///             let (i, b) = u32::from_der(i)?;
+    ///             Ok((i, (a, b)))
+    ///         }
+    ///     )
+    /// }
+    /// ```
+    pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+        E: From<Error>,
+    {
+        let (rem, seq) = Sequence::from_der(bytes).map_err(Err::convert)?;
+        let data = match seq.content {
+            Cow::Borrowed(b) => b,
+            // Since 'any' is built from 'bytes', it is borrowed by construction
+            Cow::Owned(_) => unreachable!(),
+        };
+        let (_, res) = op(data)?;
+        Ok((rem, res))
+    }
+
+    /// Apply the parsing function to the sequence content (non-consuming version)
+    pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E>
+    where
+        F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+    {
+        let input: &[u8] = &self.content;
+        f(input)
+    }
+
+    /// Apply the parsing function to the sequence content (consuming version)
+    ///
+    /// Note: to parse and apply a parsing function in one step, use the
+    /// [`Sequence::from_der_and_then`] method.
+    ///
+    /// # Limitations
+    ///
+    /// This function fails if the sequence contains `Owned` data, because the parsing function
+    /// takes a reference on data (which is dropped).
+    pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E>
+    where
+        F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+        E: From<Error>,
+    {
+        match self.content {
+            Cow::Borrowed(b) => f(b),
+            _ => Err(nom::Err::Error(Error::LifetimeError.into())),
+        }
+    }
+
+    /// Return an iterator over the sequence content, attempting to decode objects as BER
+    ///
+    /// This method can be used when all objects from the sequence have the same type.
+    pub fn ber_iter<T, E>(&'a self) -> SequenceIterator<'a, T, BerParser, E>
+    where
+        T: FromBer<'a, E>,
+    {
+        SequenceIterator::new(&self.content)
+    }
+
+    /// Return an iterator over the sequence content, attempting to decode objects as DER
+    ///
+    /// This method can be used when all objects from the sequence have the same type.
+    pub fn der_iter<T, E>(&'a self) -> SequenceIterator<'a, T, DerParser, E>
+    where
+        T: FromDer<'a, E>,
+    {
+        SequenceIterator::new(&self.content)
+    }
+
+    /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER), and return the parsed items as a `Vec`.
+    pub fn ber_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E>
+    where
+        T: FromBer<'a, E>,
+        E: From<Error>,
+    {
+        self.ber_iter().collect()
+    }
+
+    /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER), and return the parsed items as a `Vec`.
+    pub fn der_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E>
+    where
+        T: FromDer<'a, E>,
+        E: From<Error>,
+    {
+        self.der_iter().collect()
+    }
+
+    /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER) (consuming input),
+    /// and return the parsed items as a `Vec`.
+    ///
+    /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+    pub fn into_ber_sequence_of<T, U, E>(self) -> Result<Vec<T>, E>
+    where
+        for<'b> T: FromBer<'b, E>,
+        E: From<Error>,
+        T: ToStatic<Owned = T>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SequenceIterator::<T, BerParser, E>::new(bytes).collect(),
+            Cow::Owned(data) => {
+                let v1 = SequenceIterator::<T, BerParser, E>::new(&data)
+                    .collect::<Result<Vec<T>, E>>()?;
+                let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+                Ok(v2)
+            }
+        }
+    }
+
+    /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER) (consuming input),
+    /// and return the parsed items as a `Vec`.
+    ///
+    /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+    pub fn into_der_sequence_of<T, U, E>(self) -> Result<Vec<T>, E>
+    where
+        for<'b> T: FromDer<'b, E>,
+        E: From<Error>,
+        T: ToStatic<Owned = T>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(),
+            Cow::Owned(data) => {
+                let v1 = SequenceIterator::<T, DerParser, E>::new(&data)
+                    .collect::<Result<Vec<T>, E>>()?;
+                let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+                Ok(v2)
+            }
+        }
+    }
+
+    pub fn into_der_sequence_of_ref<T, E>(self) -> Result<Vec<T>, E>
+    where
+        T: FromDer<'a, E>,
+        E: From<Error>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(),
+            Cow::Owned(_) => Err(Error::LifetimeError.into()),
+        }
+    }
+}
+
+impl<'a> ToStatic for Sequence<'a> {
+    type Owned = Sequence<'static>;
+
+    fn to_static(&self) -> Self::Owned {
+        Sequence {
+            content: Cow::Owned(self.content.to_vec()),
+        }
+    }
+}
+
+impl<T, U> ToStatic for Vec<T>
+where
+    T: ToStatic<Owned = U>,
+    U: 'static,
+{
+    type Owned = Vec<U>;
+
+    fn to_static(&self) -> Self::Owned {
+        self.iter().map(|t| t.to_static()).collect()
+    }
+}
+
+impl<'a> AsRef<[u8]> for Sequence<'a> {
+    fn as_ref(&self) -> &[u8] {
+        &self.content
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Sequence<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Sequence<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Sequence<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Sequence<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        Ok(Sequence {
+            content: Cow::Borrowed(any.data),
+        })
+    }
+}
+
+impl<'a> CheckDerConstraints for Sequence<'a> {
+    fn check_constraints(_any: &Any) -> Result<()> {
+        // TODO: iterate on ANY objects and check constraints? -> this will not be exhaustive
+        // test, for ex INTEGER encoding will not be checked
+        Ok(())
+    }
+}
+
+impl<'a> DerAutoDerive for Sequence<'a> {}
+
+impl<'a> Tagged for Sequence<'a> {
+    const TAG: Tag = Tag::Sequence;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Sequence<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.content.len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            true,
+            Self::TAG,
+            Length::Definite(self.content.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&self.content).map_err(Into::into)
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Sequence<'a> {
+    /// Attempt to create a `Sequence` from an iterator over serializable objects (to DER)
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use asn1_rs::Sequence;
+    ///
+    /// // build sequence
+    /// let it = [2, 3, 4].iter();
+    /// let seq = Sequence::from_iter_to_der(it).unwrap();
+    /// ```
+    pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self>
+    where
+        IT: Iterator<Item = T>,
+        T: ToDer,
+        T: Tagged,
+    {
+        let mut v = Vec::new();
+        for item in it {
+            let item_v = <T as ToDer>::to_der_vec(&item)?;
+            v.extend_from_slice(&item_v);
+        }
+        Ok(Sequence {
+            content: Cow::Owned(v),
+        })
+    }
+}
diff --git a/src/asn1_types/sequence/iterator.rs b/src/asn1_types/sequence/iterator.rs
new file mode 100644
index 0000000..0ff5fc9
--- /dev/null
+++ b/src/asn1_types/sequence/iterator.rs
@@ -0,0 +1,106 @@
+use crate::{ASN1Parser, BerParser, DerParser, Error, FromBer, FromDer};
+use core::marker::PhantomData;
+
+/// An Iterator over binary data, parsing elements of type `T`
+///
+/// This helps parsing `SEQUENCE OF` items of type `T`. The type of parser
+/// (BER/DER) is specified using the generic parameter `F` of this struct.
+///
+/// Note: the iterator must start on the sequence *contents*, not the sequence itself.
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{DerParser, Integer, SequenceIterator};
+///
+/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2];
+/// for (idx, item) in SequenceIterator::<Integer, DerParser>::new(&data[2..]).enumerate() {
+///     let item = item.unwrap(); // parsing could have failed
+///     let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32
+///     assert_eq!(i as usize, idx + 1);
+/// }
+/// ```
+#[derive(Debug)]
+pub struct SequenceIterator<'a, T, F, E = Error>
+where
+    F: ASN1Parser,
+{
+    data: &'a [u8],
+    has_error: bool,
+    _t: PhantomData<T>,
+    _f: PhantomData<F>,
+    _e: PhantomData<E>,
+}
+
+impl<'a, T, F, E> SequenceIterator<'a, T, F, E>
+where
+    F: ASN1Parser,
+{
+    pub fn new(data: &'a [u8]) -> Self {
+        SequenceIterator {
+            data,
+            has_error: false,
+            _t: PhantomData,
+            _f: PhantomData,
+            _e: PhantomData,
+        }
+    }
+}
+
+impl<'a, T, E> Iterator for SequenceIterator<'a, T, BerParser, E>
+where
+    T: FromBer<'a, E>,
+    E: From<Error>,
+{
+    type Item = Result<T, E>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.has_error || self.data.is_empty() {
+            return None;
+        }
+        match T::from_ber(self.data) {
+            Ok((rem, obj)) => {
+                self.data = rem;
+                Some(Ok(obj))
+            }
+            Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
+                self.has_error = true;
+                Some(Err(e))
+            }
+
+            Err(nom::Err::Incomplete(n)) => {
+                self.has_error = true;
+                Some(Err(Error::Incomplete(n).into()))
+            }
+        }
+    }
+}
+
+impl<'a, T, E> Iterator for SequenceIterator<'a, T, DerParser, E>
+where
+    T: FromDer<'a, E>,
+    E: From<Error>,
+{
+    type Item = Result<T, E>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.has_error || self.data.is_empty() {
+            return None;
+        }
+        match T::from_der(self.data) {
+            Ok((rem, obj)) => {
+                self.data = rem;
+                Some(Ok(obj))
+            }
+            Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
+                self.has_error = true;
+                Some(Err(e))
+            }
+
+            Err(nom::Err::Incomplete(n)) => {
+                self.has_error = true;
+                Some(Err(Error::Incomplete(n).into()))
+            }
+        }
+    }
+}
diff --git a/src/asn1_types/sequence/sequence_of.rs b/src/asn1_types/sequence/sequence_of.rs
new file mode 100644
index 0000000..239e72f
--- /dev/null
+++ b/src/asn1_types/sequence/sequence_of.rs
@@ -0,0 +1,150 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// The `SEQUENCE OF` object is an ordered list of homogeneous types.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::SequenceOf;
+/// use std::iter::FromIterator;
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let seq = SequenceOf::from_iter(it);
+///
+/// // `seq` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in seq.iter() {
+///     // item has type `Result<u32>`, since parsing the serialized bytes could fail
+///     sum += *item;
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+#[derive(Debug)]
+pub struct SequenceOf<T> {
+    pub(crate) items: Vec<T>,
+}
+
+impl<T> SequenceOf<T> {
+    /// Builds a `SEQUENCE OF` from the provided content
+    #[inline]
+    pub const fn new(items: Vec<T>) -> Self {
+        SequenceOf { items }
+    }
+
+    /// Returns the length of this `SEQUENCE` (the number of items).
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.items.len()
+    }
+
+    /// Returns `true` if this `SEQUENCE` is empty.
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.items.is_empty()
+    }
+
+    /// Returns an iterator over the items of the `SEQUENCE`.
+    #[inline]
+    pub fn iter(&self) -> impl Iterator<Item = &T> {
+        self.items.iter()
+    }
+}
+
+impl<T> AsRef<[T]> for SequenceOf<T> {
+    fn as_ref(&self) -> &[T] {
+        &self.items
+    }
+}
+
+impl<'a, T> IntoIterator for &'a SequenceOf<T> {
+    type Item = &'a T;
+    type IntoIter = core::slice::Iter<'a, T>;
+
+    fn into_iter(self) -> core::slice::Iter<'a, T> {
+        self.items.iter()
+    }
+}
+
+impl<'a, T> IntoIterator for &'a mut SequenceOf<T> {
+    type Item = &'a mut T;
+    type IntoIter = core::slice::IterMut<'a, T>;
+
+    fn into_iter(self) -> core::slice::IterMut<'a, T> {
+        self.items.iter_mut()
+    }
+}
+
+impl<T> From<SequenceOf<T>> for Vec<T> {
+    fn from(set: SequenceOf<T>) -> Self {
+        set.items
+    }
+}
+
+impl<T> FromIterator<T> for SequenceOf<T> {
+    fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
+        let items = iter.into_iter().collect();
+        SequenceOf::new(items)
+    }
+}
+
+impl<'a, T> TryFrom<Any<'a>> for SequenceOf<T>
+where
+    T: FromBer<'a>,
+{
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        if !any.header.is_constructed() {
+            return Err(Error::ConstructExpected);
+        }
+        let items = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+        Ok(SequenceOf::new(items))
+    }
+}
+
+impl<T> CheckDerConstraints for SequenceOf<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        for item in SequenceIterator::<Any, DerParser>::new(any.data) {
+            let item = item?;
+            T::check_constraints(&item)?;
+        }
+        Ok(())
+    }
+}
+
+impl<T> DerAutoDerive for SequenceOf<T> {}
+
+impl<T> Tagged for SequenceOf<T> {
+    const TAG: Tag = Tag::Sequence;
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for SequenceOf<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        self.items.to_der_len()
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.items.write_der_header(writer)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.items.write_der_content(writer)
+    }
+}
diff --git a/src/asn1_types/sequence/vec.rs b/src/asn1_types/sequence/vec.rs
new file mode 100644
index 0000000..d8beead
--- /dev/null
+++ b/src/asn1_types/sequence/vec.rs
@@ -0,0 +1,138 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+// // XXX this compiles but requires bound TryFrom :/
+// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T>
+// where
+//     T: TryFrom<&'b Any<'a>>,
+//     for<'e> <T as TryFrom<&'b Any<'a>>>::Error: From<Error>,
+//     T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>,
+//     //     T: FromBer<'a, E>,
+//     //     E: From<Error>,
+// {
+//     type Error = <T as TryFrom<&'b Any<'a>>>::Error;
+
+//     fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> {
+//         any.tag().assert_eq(Self::TAG)?;
+//         any.header.assert_constructed()?;
+//         let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data)
+//             .collect::<Result<Vec<T>, Self::Error>>()?;
+//         Ok(v)
+//     }
+// }
+
+// // XXX this compiles but requires bound TryFrom :/
+// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T>
+// where
+//     T: TryFrom<&'b Any<'a>>,
+//     <T as TryFrom<&'b Any<'a>>>::Error: From<Error>,
+//     T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>,
+//     //     T: FromBer<'a, E>,
+//     //     E: From<Error>,
+// {
+//     type Error = <T as TryFrom<&'b Any<'a>>>::Error;
+
+//     fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> {
+//         any.tag().assert_eq(Self::TAG)?;
+//         any.header.assert_constructed()?;
+//         let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data)
+//             .collect::<Result<Vec<T>, Self::Error>>()?;
+//         Ok(v)
+//     }
+// }
+
+impl<'a, T> TryFrom<Any<'a>> for Vec<T>
+where
+    T: FromBer<'a>,
+{
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+        Ok(items)
+    }
+}
+
+impl<T> CheckDerConstraints for Vec<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        for item in SequenceIterator::<Any, DerParser>::new(any.data) {
+            let item = item?;
+            <T as CheckDerConstraints>::check_constraints(&item)?;
+        }
+        Ok(())
+    }
+}
+
+impl<T> Tagged for Vec<T> {
+    const TAG: Tag = Tag::Sequence;
+}
+
+// impl<'a, T> FromBer<'a> for Vec<T>
+// where
+//     T: FromBer<'a>,
+// {
+//     fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+//         let (rem, any) = Any::from_ber(bytes)?;
+//         any.header.assert_tag(Self::TAG)?;
+//         let v = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+//         Ok((rem, v))
+//     }
+// }
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for Vec<T>
+where
+    T: FromDer<'a, E>,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.header
+            .assert_tag(Self::TAG)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let v = SequenceIterator::<T, DerParser, E>::new(any.data)
+            .collect::<Result<Vec<T>, E>>()
+            .map_err(nom::Err::Error)?;
+        Ok((rem, v))
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for Vec<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len()?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        Ok(header.to_der_len()? + len)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut sz = 0;
+        for t in self.iter() {
+            sz += t.write_der(writer)?;
+        }
+        Ok(sz)
+    }
+}
diff --git a/src/asn1_types/set.rs b/src/asn1_types/set.rs
new file mode 100644
index 0000000..ef693d2
--- /dev/null
+++ b/src/asn1_types/set.rs
@@ -0,0 +1,387 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+mod btreeset;
+mod hashset;
+mod iterator;
+mod set_of;
+
+pub use btreeset::*;
+#[cfg(feature = "std")]
+pub use hashset::*;
+pub use iterator::*;
+pub use set_of::*;
+
+/// The `SET` object is an unordered list of heteregeneous types.
+///
+/// Sets can usually be of 2 types:
+/// - a list of different objects (`SET`, usually parsed as a `struct`)
+/// - a list of similar objects (`SET OF`, usually parsed as a `BTreeSet<T>` or `HashSet<T>`)
+///
+/// The current object covers the former. For the latter, see the [`SetOf`] documentation.
+///
+/// The `Set` object contains the (*unparsed*) encoded representation of its content. It provides
+/// methods to parse and iterate contained objects, or convert the sequence to other types.
+///
+/// # Building a Set
+///
+/// To build a DER set:
+/// - if the set is composed of objects of the same type, the [`Set::from_iter_to_der`] method can be used
+/// - otherwise, the [`ToDer`] trait can be used to create content incrementally
+///
+/// ```
+/// use asn1_rs::{Integer, Set, SerializeResult, ToDer};
+///
+/// fn build_set<'a>() -> SerializeResult<Set<'a>> {
+///     let mut v = Vec::new();
+///     // add an Integer object (construct type):
+///     let i = Integer::from_u32(4);
+///     let _ = i.write_der(&mut v)?;
+///     // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`:
+///     let _ = "abcd".write_der(&mut v)?;
+///     // return the set built from the DER content
+///     Ok(Set::new(v.into()))
+/// }
+///
+/// let seq = build_set().unwrap();
+///
+/// ```
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Error, Set};
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let set = Set::from_iter_to_der(it).unwrap();
+///
+/// // `set` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in set.der_iter::<u32, Error>() {
+///     // item has type `Result<u32>`, since parsing the serialized bytes could fail
+///     sum += item.expect("parsing list item failed");
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+///
+/// Note: the above example encodes a `SET OF INTEGER` object, the [`SetOf`] object could
+/// be used to provide a simpler API.
+///
+#[derive(Clone, Debug)]
+pub struct Set<'a> {
+    /// Serialized DER representation of the set content
+    pub content: Cow<'a, [u8]>,
+}
+
+impl<'a> Set<'a> {
+    /// Build a set, given the provided content
+    pub const fn new(content: Cow<'a, [u8]>) -> Self {
+        Set { content }
+    }
+
+    /// Consume the set and return the content
+    #[inline]
+    pub fn into_content(self) -> Cow<'a, [u8]> {
+        self.content
+    }
+
+    /// Apply the parsing function to the set content, consuming the set
+    ///
+    /// Note: this function expects the caller to take ownership of content.
+    /// In some cases, handling the lifetime of objects is not easy (when keeping only references on
+    /// data). Other methods are provided (depending on the use case):
+    /// - [`Set::parse`] takes a reference on the set data, but does not consume it,
+    /// - [`Set::from_der_and_then`] does the parsing of the set and applying the function
+    ///   in one step, ensuring there are only references (and dropping the temporary set).
+    pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>,
+    {
+        op(self.content)
+    }
+
+    /// Same as [`Set::from_der_and_then`], but using BER encoding (no constraints).
+    pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+        E: From<Error>,
+    {
+        let (rem, seq) = Set::from_ber(bytes).map_err(Err::convert)?;
+        let data = match seq.content {
+            Cow::Borrowed(b) => b,
+            // Since 'any' is built from 'bytes', it is borrowed by construction
+            Cow::Owned(_) => unreachable!(),
+        };
+        let (_, res) = op(data)?;
+        Ok((rem, res))
+    }
+
+    /// Parse a DER set and apply the provided parsing function to content
+    ///
+    /// After parsing, the set object and header are discarded.
+    ///
+    /// ```
+    /// use asn1_rs::{FromDer, ParseResult, Set};
+    ///
+    /// // Parse a SET {
+    /// //      a INTEGER (0..255),
+    /// //      b INTEGER (0..4294967296)
+    /// // }
+    /// // and return only `(a,b)
+    /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> {
+    ///     Set::from_der_and_then(i, |i| {
+    ///             let (i, a) = u8::from_der(i)?;
+    ///             let (i, b) = u32::from_der(i)?;
+    ///             Ok((i, (a, b)))
+    ///         }
+    ///     )
+    /// }
+    /// ```
+    pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+        E: From<Error>,
+    {
+        let (rem, seq) = Set::from_der(bytes).map_err(Err::convert)?;
+        let data = match seq.content {
+            Cow::Borrowed(b) => b,
+            // Since 'any' is built from 'bytes', it is borrowed by construction
+            Cow::Owned(_) => unreachable!(),
+        };
+        let (_, res) = op(data)?;
+        Ok((rem, res))
+    }
+
+    /// Apply the parsing function to the set content (non-consuming version)
+    pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E>
+    where
+        F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+    {
+        let input: &[u8] = &self.content;
+        f(input)
+    }
+
+    /// Apply the parsing function to the set content (consuming version)
+    ///
+    /// Note: to parse and apply a parsing function in one step, use the
+    /// [`Set::from_der_and_then`] method.
+    ///
+    /// # Limitations
+    ///
+    /// This function fails if the set contains `Owned` data, because the parsing function
+    /// takes a reference on data (which is dropped).
+    pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E>
+    where
+        F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+        E: From<Error>,
+    {
+        match self.content {
+            Cow::Borrowed(b) => f(b),
+            _ => Err(nom::Err::Error(Error::LifetimeError.into())),
+        }
+    }
+
+    /// Return an iterator over the set content, attempting to decode objects as BER
+    ///
+    /// This method can be used when all objects from the set have the same type.
+    pub fn ber_iter<T, E>(&'a self) -> SetIterator<'a, T, BerParser, E>
+    where
+        T: FromBer<'a, E>,
+    {
+        SetIterator::new(&self.content)
+    }
+
+    /// Return an iterator over the set content, attempting to decode objects as DER
+    ///
+    /// This method can be used when all objects from the set have the same type.
+    pub fn der_iter<T, E>(&'a self) -> SetIterator<'a, T, DerParser, E>
+    where
+        T: FromDer<'a, E>,
+    {
+        SetIterator::new(&self.content)
+    }
+
+    /// Attempt to parse the set as a `SET OF` items (BER), and return the parsed items as a `Vec`.
+    pub fn ber_set_of<T, E>(&'a self) -> Result<Vec<T>, E>
+    where
+        T: FromBer<'a, E>,
+        E: From<Error>,
+    {
+        self.ber_iter().collect()
+    }
+
+    /// Attempt to parse the set as a `SET OF` items (DER), and return the parsed items as a `Vec`.
+    pub fn der_set_of<T, E>(&'a self) -> Result<Vec<T>, E>
+    where
+        T: FromDer<'a, E>,
+        E: From<Error>,
+    {
+        self.der_iter().collect()
+    }
+
+    /// Attempt to parse the set as a `SET OF` items (BER) (consuming input),
+    /// and return the parsed items as a `Vec`.
+    ///
+    /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+    pub fn into_ber_set_of<T, E>(self) -> Result<Vec<T>, E>
+    where
+        for<'b> T: FromBer<'b, E>,
+        E: From<Error>,
+        T: ToStatic<Owned = T>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SetIterator::<T, BerParser, E>::new(bytes).collect(),
+            Cow::Owned(data) => {
+                let v1 =
+                    SetIterator::<T, BerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?;
+                let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+                Ok(v2)
+            }
+        }
+    }
+
+    /// Attempt to parse the set as a `SET OF` items (DER) (consuming input),
+    /// and return the parsed items as a `Vec`.
+    ///
+    /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+    pub fn into_der_set_of<T, E>(self) -> Result<Vec<T>, E>
+    where
+        for<'b> T: FromDer<'b, E>,
+        E: From<Error>,
+        T: ToStatic<Owned = T>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(),
+            Cow::Owned(data) => {
+                let v1 =
+                    SetIterator::<T, DerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?;
+                let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+                Ok(v2)
+            }
+        }
+    }
+
+    pub fn into_der_set_of_ref<T, E>(self) -> Result<Vec<T>, E>
+    where
+        T: FromDer<'a, E>,
+        E: From<Error>,
+    {
+        match self.content {
+            Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(),
+            Cow::Owned(_) => Err(Error::LifetimeError.into()),
+        }
+    }
+}
+
+impl<'a> ToStatic for Set<'a> {
+    type Owned = Set<'static>;
+
+    fn to_static(&self) -> Self::Owned {
+        Set {
+            content: Cow::Owned(self.content.to_vec()),
+        }
+    }
+}
+
+impl<'a> AsRef<[u8]> for Set<'a> {
+    fn as_ref(&self) -> &[u8] {
+        &self.content
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for Set<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Set<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Set<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Set<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        Ok(Set {
+            content: Cow::Borrowed(any.data),
+        })
+    }
+}
+
+impl<'a> CheckDerConstraints for Set<'a> {
+    fn check_constraints(_any: &Any) -> Result<()> {
+        Ok(())
+    }
+}
+
+impl<'a> DerAutoDerive for Set<'a> {}
+
+impl<'a> Tagged for Set<'a> {
+    const TAG: Tag = Tag::Set;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Set<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.content.len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            true,
+            Self::TAG,
+            Length::Definite(self.content.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(&self.content).map_err(Into::into)
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Set<'a> {
+    /// Attempt to create a `Set` from an iterator over serializable objects (to DER)
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use asn1_rs::Set;
+    ///
+    /// // build set
+    /// let it = [2, 3, 4].iter();
+    /// let seq = Set::from_iter_to_der(it).unwrap();
+    /// ```
+    pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self>
+    where
+        IT: Iterator<Item = T>,
+        T: ToDer,
+        T: Tagged,
+    {
+        let mut v = Vec::new();
+        for item in it {
+            let item_v = <T as ToDer>::to_der_vec(&item)?;
+            v.extend_from_slice(&item_v);
+        }
+        Ok(Set {
+            content: Cow::Owned(v),
+        })
+    }
+}
diff --git a/src/asn1_types/set/btreeset.rs b/src/asn1_types/set/btreeset.rs
new file mode 100644
index 0000000..a70f9f9
--- /dev/null
+++ b/src/asn1_types/set/btreeset.rs
@@ -0,0 +1,124 @@
+use crate::*;
+use alloc::collections::BTreeSet;
+use core::convert::TryFrom;
+
+impl<T> Tagged for BTreeSet<T> {
+    const TAG: Tag = Tag::Set;
+}
+
+impl<'a, T> TryFrom<Any<'a>> for BTreeSet<T>
+where
+    T: FromBer<'a>,
+    T: Ord,
+{
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<BTreeSet<T>>>()?;
+        Ok(items)
+    }
+}
+
+impl<T> CheckDerConstraints for BTreeSet<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        for item in SetIterator::<Any, DerParser>::new(any.data) {
+            let item = item?;
+            T::check_constraints(&item)?;
+        }
+        Ok(())
+    }
+}
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for BTreeSet<T>
+where
+    T: FromDer<'a, E>,
+    T: Ord,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Self::TAG)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        any.header
+            .assert_constructed()
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let items = SetIterator::<T, DerParser, E>::new(any.data)
+            .collect::<Result<BTreeSet<T>, E>>()
+            .map_err(nom::Err::Error)?;
+        Ok((rem, items))
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for BTreeSet<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len()?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        Ok(header.to_der_len()? + len)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut sz = 0;
+        for t in self.iter() {
+            sz += t.write_der(writer)?;
+        }
+        Ok(sz)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use core::convert::TryFrom;
+    use hex_literal::hex;
+    use std::collections::BTreeSet;
+
+    #[test]
+    fn ber_btreeset() {
+        let input = &hex! {"31 06 02 01 00 02 01 01"};
+        let (_, any) = Any::from_ber(input).expect("parsing hashset failed");
+        <BTreeSet<u32>>::check_constraints(&any).unwrap();
+
+        let h = <BTreeSet<u32>>::try_from(any).unwrap();
+
+        assert_eq!(h.len(), 2);
+    }
+
+    #[test]
+    fn der_btreeset() {
+        let input = &hex! {"31 06 02 01 00 02 01 01"};
+        let r: IResult<_, _, Error> = BTreeSet::<u32>::from_der(input);
+        let (_, h) = r.expect("parsing hashset failed");
+
+        assert_eq!(h.len(), 2);
+
+        assert_eq!(h.to_der_len(), Ok(8));
+        let v = h.to_der_vec().expect("could not serialize");
+        let (_, h2) = SetOf::<u32>::from_der(&v).unwrap();
+        assert!(h.iter().eq(h2.iter()));
+    }
+}
diff --git a/src/asn1_types/set/hashset.rs b/src/asn1_types/set/hashset.rs
new file mode 100644
index 0000000..d505301
--- /dev/null
+++ b/src/asn1_types/set/hashset.rs
@@ -0,0 +1,125 @@
+#![cfg(feature = "std")]
+use crate::*;
+use std::collections::HashSet;
+use std::convert::TryFrom;
+use std::hash::Hash;
+
+impl<T> Tagged for HashSet<T> {
+    const TAG: Tag = Tag::Set;
+}
+
+impl<'a, T> TryFrom<Any<'a>> for HashSet<T>
+where
+    T: FromBer<'a>,
+    T: Hash + Eq,
+{
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<HashSet<T>>>()?;
+        Ok(items)
+    }
+}
+
+impl<T> CheckDerConstraints for HashSet<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        for item in SetIterator::<Any, DerParser>::new(any.data) {
+            let item = item?;
+            T::check_constraints(&item)?;
+        }
+        Ok(())
+    }
+}
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for HashSet<T>
+where
+    T: FromDer<'a, E>,
+    T: Hash + Eq,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Self::TAG)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        any.header
+            .assert_constructed()
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let items = SetIterator::<T, DerParser, E>::new(any.data)
+            .collect::<Result<HashSet<T>, E>>()
+            .map_err(nom::Err::Error)?;
+        Ok((rem, items))
+    }
+}
+
+impl<T> ToDer for HashSet<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len()?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        Ok(header.to_der_len()? + len)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut len = 0;
+        for t in self.iter() {
+            len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+        }
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut sz = 0;
+        for t in self.iter() {
+            sz += t.write_der(writer)?;
+        }
+        Ok(sz)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use core::convert::TryFrom;
+    use hex_literal::hex;
+    use std::collections::HashSet;
+
+    #[test]
+    fn ber_hashset() {
+        let input = &hex! {"31 06 02 01 00 02 01 01"};
+        let (_, any) = Any::from_ber(input).expect("parsing hashset failed");
+        <HashSet<u32>>::check_constraints(&any).unwrap();
+
+        let h = <HashSet<u32>>::try_from(any).unwrap();
+
+        assert_eq!(h.len(), 2);
+    }
+
+    #[test]
+    fn der_hashset() {
+        let input = &hex! {"31 06 02 01 00 02 01 01"};
+        let r: IResult<_, _, Error> = HashSet::<u32>::from_der(input);
+        let (_, h) = r.expect("parsing hashset failed");
+
+        assert_eq!(h.len(), 2);
+
+        assert_eq!(h.to_der_len(), Ok(8));
+        let v = h.to_der_vec().expect("could not serialize");
+        let (_, h2) = SetOf::<u32>::from_der(&v).unwrap();
+        assert!(h.iter().eq(h2.iter()));
+    }
+}
diff --git a/src/asn1_types/set/iterator.rs b/src/asn1_types/set/iterator.rs
new file mode 100644
index 0000000..eb46075
--- /dev/null
+++ b/src/asn1_types/set/iterator.rs
@@ -0,0 +1,22 @@
+pub use crate::{Error, SequenceIterator};
+
+/// An Iterator over binary data, parsing elements of type `T`
+///
+/// This helps parsing `SET OF` items of type `T`. The type of parser
+/// (BER/DER) is specified using the generic parameter `F` of this struct.
+///
+/// Note: the iterator must start on the set *contents*, not the set itself.
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{DerParser, Integer, SetIterator};
+///
+/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2];
+/// for (idx, item) in SetIterator::<Integer, DerParser>::new(&data[2..]).enumerate() {
+///     let item = item.unwrap(); // parsing could have failed
+///     let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32
+///     assert_eq!(i as usize, idx + 1);
+/// }
+/// ```
+pub type SetIterator<'a, T, F, E = Error> = SequenceIterator<'a, T, F, E>;
diff --git a/src/asn1_types/set/set_of.rs b/src/asn1_types/set/set_of.rs
new file mode 100644
index 0000000..af6c0a7
--- /dev/null
+++ b/src/asn1_types/set/set_of.rs
@@ -0,0 +1,150 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// The `SET OF` object is an unordered list of homogeneous types.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::SetOf;
+/// use std::iter::FromIterator;
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let set = SetOf::from_iter(it);
+///
+/// // `set` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in set.iter() {
+///     // item has type `Result<u32>`, since parsing the serialized bytes could fail
+///     sum += *item;
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+#[derive(Debug)]
+pub struct SetOf<T> {
+    items: Vec<T>,
+}
+
+impl<T> SetOf<T> {
+    /// Builds a `SET OF` from the provided content
+    #[inline]
+    pub const fn new(items: Vec<T>) -> Self {
+        SetOf { items }
+    }
+
+    /// Returns the length of this `SET` (the number of items).
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.items.len()
+    }
+
+    /// Returns `true` if this `SET` is empty.
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.items.is_empty()
+    }
+
+    /// Returns an iterator over the items of the `SET`.
+    #[inline]
+    pub fn iter(&self) -> impl Iterator<Item = &T> {
+        self.items.iter()
+    }
+}
+
+impl<T> AsRef<[T]> for SetOf<T> {
+    fn as_ref(&self) -> &[T] {
+        &self.items
+    }
+}
+
+impl<'a, T> IntoIterator for &'a SetOf<T> {
+    type Item = &'a T;
+    type IntoIter = core::slice::Iter<'a, T>;
+
+    fn into_iter(self) -> core::slice::Iter<'a, T> {
+        self.items.iter()
+    }
+}
+
+impl<'a, T> IntoIterator for &'a mut SetOf<T> {
+    type Item = &'a mut T;
+    type IntoIter = core::slice::IterMut<'a, T>;
+
+    fn into_iter(self) -> core::slice::IterMut<'a, T> {
+        self.items.iter_mut()
+    }
+}
+
+impl<T> From<SetOf<T>> for Vec<T> {
+    fn from(set: SetOf<T>) -> Self {
+        set.items
+    }
+}
+
+impl<T> FromIterator<T> for SetOf<T> {
+    fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
+        let items = iter.into_iter().collect();
+        SetOf::new(items)
+    }
+}
+
+impl<'a, T> TryFrom<Any<'a>> for SetOf<T>
+where
+    T: FromBer<'a>,
+{
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self> {
+        any.tag().assert_eq(Self::TAG)?;
+        if !any.header.is_constructed() {
+            return Err(Error::ConstructExpected);
+        }
+        let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+        Ok(SetOf::new(items))
+    }
+}
+
+impl<T> CheckDerConstraints for SetOf<T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.tag().assert_eq(Self::TAG)?;
+        any.header.assert_constructed()?;
+        for item in SetIterator::<Any, DerParser>::new(any.data) {
+            let item = item?;
+            T::check_constraints(&item)?;
+        }
+        Ok(())
+    }
+}
+
+impl<T> DerAutoDerive for SetOf<T> {}
+
+impl<T> Tagged for SetOf<T> {
+    const TAG: Tag = Tag::Set;
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for SetOf<T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        self.items.to_der_len()
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.items.write_der_header(writer)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.items.write_der_content(writer)
+    }
+}
diff --git a/src/asn1_types/strings.rs b/src/asn1_types/strings.rs
new file mode 100644
index 0000000..66a4c19
--- /dev/null
+++ b/src/asn1_types/strings.rs
@@ -0,0 +1,171 @@
+mod bmpstring;
+mod generalstring;
+mod graphicstring;
+mod ia5string;
+mod numericstring;
+mod printablestring;
+mod str;
+mod string;
+mod teletexstring;
+mod universalstring;
+mod utf8string;
+mod videotexstring;
+mod visiblestring;
+
+pub use self::str::*;
+pub use bmpstring::*;
+pub use generalstring::*;
+pub use graphicstring::*;
+pub use ia5string::*;
+pub use numericstring::*;
+pub use printablestring::*;
+pub use string::*;
+pub use teletexstring::*;
+pub use universalstring::*;
+pub use utf8string::*;
+pub use videotexstring::*;
+pub use visiblestring::*;
+
+/// Base trait for BER string objects and character set validation
+///
+/// This trait is implemented by several types, and is used to determine if some bytes
+/// would be valid for the given type.
+///
+/// # Example
+///
+/// ```rust
+/// use asn1_rs::{PrintableString, TestValidCharset, VisibleString};
+///
+/// let bytes: &[u8] = b"abcd*4";
+/// let res = PrintableString::test_valid_charset(bytes);
+/// assert!(res.is_err());
+/// let res = VisibleString::test_valid_charset(bytes);
+/// assert!(res.is_ok());
+/// ```
+pub trait TestValidCharset {
+    /// Check character set for this object type.
+    fn test_valid_charset(i: &[u8]) -> crate::Result<()>;
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! asn1_string {
+    (IMPL $name:ident, $sname:expr) => {
+        #[doc="ASN.1 restricted character string type (`"]
+        #[doc = $sname]
+        #[doc = "`)"]
+        #[derive(Debug, PartialEq, Eq)]
+        pub struct $name<'a> {
+            pub(crate) data: alloc::borrow::Cow<'a, str>,
+        }
+
+        impl<'a> $name<'a> {
+            pub const fn new(s: &'a str) -> Self {
+                $name {
+                    data: alloc::borrow::Cow::Borrowed(s),
+                }
+            }
+
+            pub fn string(&self) -> String {
+                use alloc::string::ToString;
+                self.data.to_string()
+            }
+        }
+
+        impl<'a> AsRef<str> for $name<'a> {
+            fn as_ref(&self) -> &str {
+                &self.data
+            }
+        }
+
+        impl<'a> From<&'a str> for $name<'a> {
+            fn from(s: &'a str) -> Self {
+                Self::new(s)
+            }
+        }
+
+        impl From<String> for $name<'_> {
+            fn from(s: String) -> Self {
+                Self {
+                    data: alloc::borrow::Cow::Owned(s),
+                }
+            }
+        }
+
+        impl<'a> core::convert::TryFrom<$crate::Any<'a>> for $name<'a> {
+            type Error = $crate::Error;
+
+            fn try_from(any: $crate::Any<'a>) -> $crate::Result<$name<'a>> {
+                use core::convert::TryFrom;
+                TryFrom::try_from(&any)
+            }
+        }
+
+        impl<'a, 'b> core::convert::TryFrom<&'b $crate::Any<'a>> for $name<'a> {
+            type Error = $crate::Error;
+
+            fn try_from(any: &'b $crate::Any<'a>) -> $crate::Result<$name<'a>> {
+                use $crate::traits::Tagged;
+                use alloc::borrow::Cow;
+                any.tag().assert_eq(Self::TAG)?;
+                <$name>::test_valid_charset(any.data)?;
+
+                let s = alloc::str::from_utf8(any.data)?;
+                let data = Cow::Borrowed(s);
+                Ok($name { data })
+            }
+        }
+
+        impl<'a> $crate::CheckDerConstraints for $name<'a> {
+            fn check_constraints(any: &$crate::Any) -> $crate::Result<()> {
+                any.header.assert_primitive()?;
+                Ok(())
+            }
+        }
+
+        impl $crate::DerAutoDerive for $name<'_> {}
+
+        impl<'a> $crate::Tagged for $name<'a> {
+            const TAG: $crate::Tag = $crate::Tag::$name;
+        }
+
+        #[cfg(feature = "std")]
+        impl $crate::ToDer for $name<'_> {
+            fn to_der_len(&self) -> Result<usize> {
+                let sz = self.data.as_bytes().len();
+                if sz < 127 {
+                    // 1 (class+tag) + 1 (length) + len
+                    Ok(2 + sz)
+                } else {
+                    // 1 (class+tag) + n (length) + len
+                    let n = $crate::Length::Definite(sz).to_der_len()?;
+                    Ok(1 + n + sz)
+                }
+            }
+
+            fn write_der_header(
+                &self,
+                writer: &mut dyn std::io::Write,
+            ) -> $crate::SerializeResult<usize> {
+                use $crate::Tagged;
+                let header = $crate::Header::new(
+                    $crate::Class::Universal,
+                    false,
+                    Self::TAG,
+                    $crate::Length::Definite(self.data.len()),
+                );
+                header.write_der_header(writer).map_err(Into::into)
+            }
+
+            fn write_der_content(
+                &self,
+                writer: &mut dyn std::io::Write,
+            ) -> $crate::SerializeResult<usize> {
+                writer.write(self.data.as_bytes()).map_err(Into::into)
+            }
+        }
+    };
+    ($name:ident) => {
+        asn1_string!(IMPL $name, stringify!($name));
+    };
+}
diff --git a/src/asn1_types/strings/bmpstring.rs b/src/asn1_types/strings/bmpstring.rs
new file mode 100644
index 0000000..490c68c
--- /dev/null
+++ b/src/asn1_types/strings/bmpstring.rs
@@ -0,0 +1,132 @@
+// do not use the `asn1_string` macro, since types are not the same
+// X.680 section 37.15
+
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+
+/// ASN.1 `BMPSTRING` type
+///
+/// Note: parsing a `BmpString` allocates memory since the UTF-16 to UTF-8 conversion requires a memory allocation.
+/// (see `String::from_utf16` method).
+#[derive(Debug, PartialEq, Eq)]
+pub struct BmpString<'a> {
+    pub(crate) data: Cow<'a, str>,
+}
+
+impl<'a> BmpString<'a> {
+    pub const fn new(s: &'a str) -> Self {
+        BmpString {
+            data: Cow::Borrowed(s),
+        }
+    }
+
+    pub fn string(&self) -> String {
+        self.data.to_string()
+    }
+}
+
+impl<'a> AsRef<str> for BmpString<'a> {
+    fn as_ref(&self) -> &str {
+        &self.data
+    }
+}
+
+impl<'a> From<&'a str> for BmpString<'a> {
+    fn from(s: &'a str) -> Self {
+        Self::new(s)
+    }
+}
+
+impl From<String> for BmpString<'_> {
+    fn from(s: String) -> Self {
+        Self {
+            data: alloc::borrow::Cow::Owned(s),
+        }
+    }
+}
+
+impl<'a> core::convert::TryFrom<Any<'a>> for BmpString<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<BmpString<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+
+        // read slice as big-endian UTF-16 string
+        let v = &any
+            .data
+            .chunks(2)
+            .map(|s| match s {
+                [a, b] => ((*a as u16) << 8) | (*b as u16),
+                [a] => *a as u16,
+                _ => unreachable!(),
+            })
+            .collect::<Vec<_>>();
+
+        let s = String::from_utf16(v)?;
+        let data = Cow::Owned(s);
+
+        Ok(BmpString { data })
+    }
+}
+
+impl<'a> CheckDerConstraints for BmpString<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for BmpString<'_> {}
+
+impl<'a> Tagged for BmpString<'a> {
+    const TAG: Tag = Tag::BmpString;
+}
+
+impl<'a> TestValidCharset for BmpString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        if i.len() % 2 != 0 {
+            return Err(Error::StringInvalidCharset);
+        }
+        let iter = i.chunks(2).map(|s| ((s[0] as u16) << 8) | (s[1] as u16));
+        for c in char::decode_utf16(iter) {
+            if c.is_err() {
+                return Err(Error::StringInvalidCharset);
+            }
+        }
+        Ok(())
+    }
+}
+
+#[cfg(feature = "std")]
+impl ToDer for BmpString<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        // compute the UTF-16 length
+        let sz = self.data.encode_utf16().count() * 2;
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // compute the UTF-16 length
+        let l = self.data.encode_utf16().count() * 2;
+        let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(l));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut v = Vec::new();
+        for u in self.data.encode_utf16() {
+            v.push((u >> 8) as u8);
+            v.push((u & 0xff) as u8);
+        }
+        writer.write(&v).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/strings/generalstring.rs b/src/asn1_types/strings/generalstring.rs
new file mode 100644
index 0000000..f455d6a
--- /dev/null
+++ b/src/asn1_types/strings/generalstring.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(GeneralString);
+
+impl<'a> TestValidCharset for GeneralString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        if !i.iter().all(u8::is_ascii) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/graphicstring.rs b/src/asn1_types/strings/graphicstring.rs
new file mode 100644
index 0000000..3d84040
--- /dev/null
+++ b/src/asn1_types/strings/graphicstring.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(GraphicString);
+
+impl<'a> TestValidCharset for GraphicString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        if !i.iter().all(u8::is_ascii) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/ia5string.rs b/src/asn1_types/strings/ia5string.rs
new file mode 100644
index 0000000..4b37465
--- /dev/null
+++ b/src/asn1_types/strings/ia5string.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(Ia5String);
+
+impl<'a> TestValidCharset for Ia5String<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        if !i.iter().all(u8::is_ascii) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/numericstring.rs b/src/asn1_types/strings/numericstring.rs
new file mode 100644
index 0000000..dbe4ff1
--- /dev/null
+++ b/src/asn1_types/strings/numericstring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(NumericString);
+
+impl<'a> TestValidCharset for NumericString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_numeric(b: &u8) -> bool {
+            matches!(*b, b'0'..=b'9' | b' ')
+        }
+        if !i.iter().all(is_numeric) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/printablestring.rs b/src/asn1_types/strings/printablestring.rs
new file mode 100644
index 0000000..5cd51fa
--- /dev/null
+++ b/src/asn1_types/strings/printablestring.rs
@@ -0,0 +1,35 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(PrintableString);
+
+impl<'a> TestValidCharset for PrintableString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        // Argument must be a reference, because of the .iter().all(F) call below
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_printable(b: &u8) -> bool {
+            matches!(*b,
+            b'a'..=b'z'
+            | b'A'..=b'Z'
+            | b'0'..=b'9'
+            | b' '
+            | b'\''
+            | b'('
+            | b')'
+            | b'+'
+            | b','
+            | b'-'
+            | b'.'
+            | b'/'
+            | b':'
+            | b'='
+            | b'?')
+        }
+
+        if !i.iter().all(is_printable) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/str.rs b/src/asn1_types/strings/str.rs
new file mode 100644
index 0000000..a90e8e3
--- /dev/null
+++ b/src/asn1_types/strings/str.rs
@@ -0,0 +1,67 @@
+use crate::*;
+use alloc::borrow::Cow;
+use core::convert::TryFrom;
+
+impl<'a> TryFrom<Any<'a>> for &'a str {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<&'a str> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for &'a str {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<&'a str> {
+        any.tag().assert_eq(Self::TAG)?;
+        let s = Utf8String::try_from(any)?;
+        match s.data {
+            Cow::Borrowed(s) => Ok(s),
+            Cow::Owned(_) => Err(Error::LifetimeError),
+        }
+    }
+}
+
+impl<'a> CheckDerConstraints for &'a str {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 10.2
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for &'_ str {}
+
+impl<'a> Tagged for &'a str {
+    const TAG: Tag = Tag::Utf8String;
+}
+
+#[cfg(feature = "std")]
+impl<'a> ToDer for &'a str {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.as_bytes().len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.as_bytes().len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(self.as_bytes()).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/strings/string.rs b/src/asn1_types/strings/string.rs
new file mode 100644
index 0000000..81f6ba8
--- /dev/null
+++ b/src/asn1_types/strings/string.rs
@@ -0,0 +1,64 @@
+use crate::*;
+use alloc::string::String;
+use core::convert::TryFrom;
+
+impl<'a> TryFrom<Any<'a>> for String {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<String> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for String {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<String> {
+        any.tag().assert_eq(Self::TAG)?;
+        let s = Utf8String::try_from(any)?;
+        Ok(s.data.into_owned())
+    }
+}
+
+impl CheckDerConstraints for String {
+    fn check_constraints(any: &Any) -> Result<()> {
+        // X.690 section 10.2
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for String {}
+
+impl Tagged for String {
+    const TAG: Tag = Tag::Utf8String;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for String {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.as_bytes().len();
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.len()),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        writer.write(self.as_ref()).map_err(Into::into)
+    }
+}
diff --git a/src/asn1_types/strings/teletexstring.rs b/src/asn1_types/strings/teletexstring.rs
new file mode 100644
index 0000000..2f58804
--- /dev/null
+++ b/src/asn1_types/strings/teletexstring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(TeletexString);
+
+impl<'a> TestValidCharset for TeletexString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_visible(b: &u8) -> bool {
+            0x20 <= *b && *b <= 0x7f
+        }
+        if !i.iter().all(is_visible) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/universalstring.rs b/src/asn1_types/strings/universalstring.rs
new file mode 100644
index 0000000..9006362
--- /dev/null
+++ b/src/asn1_types/strings/universalstring.rs
@@ -0,0 +1,137 @@
+// do not use the `asn1_string` macro, since types are not the same
+// X.680 section 37.6 and X.690 section 8.21.7
+
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// ASN.1 `UniversalString` type
+///
+/// Note: parsing a `UniversalString` allocates memory since the UCS-4 to UTF-8 conversion requires a memory allocation.
+#[derive(Debug, PartialEq, Eq)]
+pub struct UniversalString<'a> {
+    pub(crate) data: Cow<'a, str>,
+}
+
+impl<'a> UniversalString<'a> {
+    pub const fn new(s: &'a str) -> Self {
+        UniversalString {
+            data: Cow::Borrowed(s),
+        }
+    }
+
+    pub fn string(&self) -> String {
+        self.data.to_string()
+    }
+}
+
+impl<'a> AsRef<str> for UniversalString<'a> {
+    fn as_ref(&self) -> &str {
+        &self.data
+    }
+}
+
+impl<'a> From<&'a str> for UniversalString<'a> {
+    fn from(s: &'a str) -> Self {
+        Self::new(s)
+    }
+}
+
+impl From<String> for UniversalString<'_> {
+    fn from(s: String) -> Self {
+        Self {
+            data: alloc::borrow::Cow::Owned(s),
+        }
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for UniversalString<'a> {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<UniversalString<'a>> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for UniversalString<'a> {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<UniversalString<'a>> {
+        any.tag().assert_eq(Self::TAG)?;
+
+        if any.data.len() % 4 != 0 {
+            return Err(Error::StringInvalidCharset);
+        }
+
+        // read slice as big-endian UCS-4 string
+        let v = &any
+            .data
+            .chunks(4)
+            .map(|s| match s {
+                [a, b, c, d] => {
+                    let u32_val = ((*a as u32) << 24)
+                        | ((*b as u32) << 16)
+                        | ((*c as u32) << 8)
+                        | (*d as u32);
+                    char::from_u32(u32_val)
+                }
+                _ => unreachable!(),
+            })
+            .collect::<Option<Vec<_>>>()
+            .ok_or(Error::StringInvalidCharset)?;
+
+        let s = String::from_iter(v);
+        let data = Cow::Owned(s);
+
+        Ok(UniversalString { data })
+    }
+}
+
+impl<'a> CheckDerConstraints for UniversalString<'a> {
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.assert_primitive()?;
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for UniversalString<'_> {}
+
+impl<'a> Tagged for UniversalString<'a> {
+    const TAG: Tag = Tag::UniversalString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for UniversalString<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        // UCS-4: 4 bytes per character
+        let sz = self.data.as_bytes().len() * 4;
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let header = Header::new(
+            Class::Universal,
+            false,
+            Self::TAG,
+            Length::Definite(self.data.as_bytes().len() * 4),
+        );
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.data
+            .chars()
+            .try_for_each(|c| writer.write(&(c as u32).to_be_bytes()[..]).map(|_| ()))?;
+        Ok(self.data.as_bytes().len() * 4)
+    }
+}
diff --git a/src/asn1_types/strings/utf8string.rs b/src/asn1_types/strings/utf8string.rs
new file mode 100644
index 0000000..0bf87d4
--- /dev/null
+++ b/src/asn1_types/strings/utf8string.rs
@@ -0,0 +1,13 @@
+use crate::asn1_string;
+use crate::Result;
+use crate::TestValidCharset;
+use alloc::string::String;
+
+asn1_string!(Utf8String);
+
+impl<'a> TestValidCharset for Utf8String<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        let _ = core::str::from_utf8(i)?;
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/videotexstring.rs b/src/asn1_types/strings/videotexstring.rs
new file mode 100644
index 0000000..51c5a46
--- /dev/null
+++ b/src/asn1_types/strings/videotexstring.rs
@@ -0,0 +1,19 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(VideotexString);
+
+impl<'a> TestValidCharset for VideotexString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_visible(b: &u8) -> bool {
+            // XXX
+            0x20 <= *b && *b <= 0x7f
+        }
+        if !i.iter().all(is_visible) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/strings/visiblestring.rs b/src/asn1_types/strings/visiblestring.rs
new file mode 100644
index 0000000..2b141dc
--- /dev/null
+++ b/src/asn1_types/strings/visiblestring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(VisibleString);
+
+impl<'a> TestValidCharset for VisibleString<'a> {
+    fn test_valid_charset(i: &[u8]) -> Result<()> {
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_visible(b: &u8) -> bool {
+            0x20 <= *b && *b <= 0x7f
+        }
+        if !i.iter().all(is_visible) {
+            return Err(Error::StringInvalidCharset);
+        }
+        Ok(())
+    }
+}
diff --git a/src/asn1_types/tagged.rs b/src/asn1_types/tagged.rs
new file mode 100644
index 0000000..c1652d4
--- /dev/null
+++ b/src/asn1_types/tagged.rs
@@ -0,0 +1,128 @@
+use crate::{Class, Error, Tag, Tagged};
+use core::marker::PhantomData;
+
+mod application;
+mod builder;
+mod explicit;
+mod helpers;
+mod implicit;
+mod optional;
+mod parser;
+mod private;
+
+pub use application::*;
+pub use builder::*;
+pub use explicit::*;
+pub use helpers::*;
+pub use implicit::*;
+pub use optional::*;
+pub use parser::*;
+pub use private::*;
+
+pub(crate) const CONTEXT_SPECIFIC: u8 = Class::ContextSpecific as u8;
+
+/// A type parameter for `IMPLICIT` tagged values.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Implicit {}
+
+/// A type parameter for `EXPLICIT` tagged values.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Explicit {}
+
+/// A type parameter for tagged values either [`Explicit`] or [`Implicit`].
+pub trait TagKind {}
+
+impl TagKind for Implicit {}
+impl TagKind for Explicit {}
+
+/// Helper object for creating `FromBer`/`FromDer` types for TAGGED OPTIONAL types
+///
+/// When parsing `ContextSpecific` (the most common class), see [`TaggedExplicit`] and
+/// [`TaggedImplicit`] alias types.
+///
+/// # Notes
+///
+/// `CLASS` must be between 0 and 4. See [`Class`] for possible values for the `CLASS` parameter.
+/// Constants from this class can be used, but they must be wrapped in braces due to
+/// [Rust syntax for generics](https://doc.rust-lang.org/reference/items/generics.html)
+/// (see example below).
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Class, Error, Explicit, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) =
+///     TaggedValue::<Integer, Error, Explicit, {Class::APPLICATION}, 0>::from_ber(bytes)
+///         .unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+#[derive(Debug, PartialEq, Eq)]
+pub struct TaggedValue<T, E, TagKind, const CLASS: u8, const TAG: u32> {
+    pub(crate) inner: T,
+
+    tag_kind: PhantomData<TagKind>,
+    _e: PhantomData<E>,
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> TaggedValue<T, E, TagKind, CLASS, TAG> {
+    /// Consumes the `TaggedParser`, returning the wrapped value.
+    #[inline]
+    pub fn into_inner(self) -> T {
+        self.inner
+    }
+
+    /// Return the (outer) tag of this object
+    pub const fn tag(&self) -> Tag {
+        Self::TAG
+    }
+
+    /// Return the (outer) class of this object
+    #[inline]
+    pub const fn class(&self) -> u8 {
+        CLASS
+    }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Explicit, CLASS, TAG> {
+    /// Constructs a new `EXPLICIT TaggedParser` with the provided value
+    #[inline]
+    pub const fn explicit(inner: T) -> Self {
+        TaggedValue {
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Implicit, CLASS, TAG> {
+    /// Constructs a new `IMPLICIT TaggedParser` with the provided value
+    #[inline]
+    pub const fn implicit(inner: T) -> Self {
+        TaggedValue {
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> AsRef<T>
+    for TaggedValue<T, E, TagKind, CLASS, TAG>
+{
+    fn as_ref(&self) -> &T {
+        &self.inner
+    }
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> Tagged
+    for TaggedValue<T, E, TagKind, CLASS, TAG>
+{
+    const TAG: Tag = Tag(TAG);
+}
diff --git a/src/asn1_types/tagged/application.rs b/src/asn1_types/tagged/application.rs
new file mode 100644
index 0000000..ddd2b07
--- /dev/null
+++ b/src/asn1_types/tagged/application.rs
@@ -0,0 +1,42 @@
+use crate::{Class, Explicit, Implicit, TaggedValue};
+
+/// A helper object to parse `[APPLICATION n] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit application-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{ApplicationExplicit, Error, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = ApplicationExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type ApplicationExplicit<T, E, const TAG: u32> =
+    TaggedValue<T, E, Explicit, { Class::APPLICATION }, TAG>;
+
+/// A helper object to parse `[APPLICATION n] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit application-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] IMPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{ApplicationImplicit, Error, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x1, 0x2];
+///
+/// let (_, tagged) = ApplicationImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8)));
+/// ```
+pub type ApplicationImplicit<T, E, const TAG: u32> =
+    TaggedValue<T, E, Implicit, { Class::APPLICATION }, TAG>;
diff --git a/src/asn1_types/tagged/builder.rs b/src/asn1_types/tagged/builder.rs
new file mode 100644
index 0000000..093b9d9
--- /dev/null
+++ b/src/asn1_types/tagged/builder.rs
@@ -0,0 +1,104 @@
+use super::{Error, Explicit, Implicit, TaggedParser};
+use crate::{Class, FromBer, FromDer, ParseResult, Tag};
+use core::marker::PhantomData;
+
+/// A builder for parsing tagged values (`IMPLICIT` or `EXPLICIT`)
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Class, Tag, TaggedParserBuilder};
+///
+/// let parser = TaggedParserBuilder::explicit()
+///     .with_class(Class::ContextSpecific)
+///     .with_tag(Tag(0))
+///     .der_parser::<u32>();
+///
+/// let input = &[0xa0, 0x03, 0x02, 0x01, 0x02];
+/// let (rem, tagged) = parser(input).expect("parsing failed");
+///
+/// assert!(rem.is_empty());
+/// assert_eq!(tagged.tag(), Tag(0));
+/// assert_eq!(tagged.as_ref(), &2);
+/// ```
+#[derive(Clone, Copy, Debug)]
+pub struct TaggedParserBuilder<TagKind, E = Error> {
+    class: Class,
+    tag: Tag,
+    tag_kind: PhantomData<TagKind>,
+    _e: PhantomData<E>,
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+    /// Create a default `TaggedParserBuilder` builder
+    ///
+    /// `TagKind` must be specified as either [`Explicit`] or [`Implicit`]
+    ///
+    /// ```
+    /// use asn1_rs::{Explicit, TaggedParserBuilder};
+    ///
+    /// let builder = TaggedParserBuilder::<Explicit>::new();
+    /// ```
+    pub const fn new() -> Self {
+        TaggedParserBuilder {
+            class: Class::Universal,
+            tag: Tag(0),
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+
+    /// Set the expected `Class` for the builder
+    pub const fn with_class(self, class: Class) -> Self {
+        Self { class, ..self }
+    }
+
+    /// Set the expected `Tag` for the builder
+    pub const fn with_tag(self, tag: Tag) -> Self {
+        Self { tag, ..self }
+    }
+}
+
+impl<E> TaggedParserBuilder<Explicit, E> {
+    /// Create a `TagParser` builder for `EXPLICIT` tagged values
+    pub const fn explicit() -> Self {
+        TaggedParserBuilder::new()
+    }
+}
+
+impl<E> TaggedParserBuilder<Implicit, E> {
+    /// Create a `TagParser` builder for `IMPLICIT` tagged values
+    pub const fn implicit() -> Self {
+        TaggedParserBuilder::new()
+    }
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+    /// Create the BER parser from the builder parameters
+    ///
+    /// This method will consume the builder and return a parser (to be used as a function).
+    pub fn ber_parser<'a, T>(
+        self,
+    ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E>
+    where
+        TaggedParser<'a, TagKind, T, E>: FromBer<'a, E>,
+        E: From<Error>,
+    {
+        move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_ber(self.class, self.tag, bytes)
+    }
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+    /// Create the DER parser from the builder parameters
+    ///
+    /// This method will consume the builder and return a parser (to be used as a function).
+    pub fn der_parser<'a, T>(
+        self,
+    ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E>
+    where
+        TaggedParser<'a, TagKind, T, E>: FromDer<'a, E>,
+        E: From<Error>,
+    {
+        move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_der(self.class, self.tag, bytes)
+    }
+}
diff --git a/src/asn1_types/tagged/explicit.rs b/src/asn1_types/tagged/explicit.rs
new file mode 100644
index 0000000..7fdbcce
--- /dev/null
+++ b/src/asn1_types/tagged/explicit.rs
@@ -0,0 +1,262 @@
+use crate::*;
+use core::convert::TryFrom;
+use core::marker::PhantomData;
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>>
+    for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+    T: FromBer<'a, E>,
+    E: From<Error>,
+{
+    type Error = E;
+
+    fn try_from(any: Any<'a>) -> Result<Self, E> {
+        Self::try_from(&any)
+    }
+}
+
+impl<'a, 'b, T, E, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>>
+    for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+    T: FromBer<'a, E>,
+    E: From<Error>,
+{
+    type Error = E;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Self, E> {
+        any.tag().assert_eq(Tag(TAG))?;
+        any.header.assert_constructed()?;
+        if any.class() as u8 != CLASS {
+            let class = Class::try_from(CLASS).ok();
+            return Err(Error::unexpected_class(class, any.class()).into());
+        }
+        let (_, inner) = match T::from_ber(any.data) {
+            Ok((rem, res)) => (rem, res),
+            Err(Err::Error(e)) | Err(Err::Failure(e)) => return Err(e),
+            Err(Err::Incomplete(n)) => return Err(Error::Incomplete(n).into()),
+        };
+        Ok(TaggedValue::explicit(inner))
+    }
+}
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E>
+    for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+    T: FromDer<'a, E>,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Tag(TAG))
+            .map_err(|e| Err::Error(e.into()))?;
+        any.header
+            .assert_constructed()
+            .map_err(|e| Err::Error(e.into()))?;
+        if any.class() as u8 != CLASS {
+            let class = Class::try_from(CLASS).ok();
+            return Err(Err::Error(
+                Error::unexpected_class(class, any.class()).into(),
+            ));
+        }
+        let (_, inner) = T::from_der(any.data)?;
+        Ok((rem, TaggedValue::explicit(inner)))
+    }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints
+    for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length.assert_definite()?;
+        let (_, inner) = Any::from_ber(any.data)?;
+        T::check_constraints(&inner)?;
+        Ok(())
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.inner.to_der_len()?;
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let inner_len = self.inner.to_der_len()?;
+        let class =
+            Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+        let header = Header::new(class, true, self.tag(), Length::Definite(inner_len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.inner.write_der(writer)
+    }
+}
+
+/// A helper object to parse `[ n ] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed.
+///
+/// # Examples
+///
+/// To parse a `[0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, TaggedExplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = TaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type TaggedExplicit<T, E, const TAG: u32> = TaggedValue<T, E, Explicit, CONTEXT_SPECIFIC, TAG>;
+
+// implementations for TaggedParser
+
+impl<'a, T, E> TaggedParser<'a, Explicit, T, E> {
+    pub const fn new_explicit(class: Class, tag: u32, inner: T) -> Self {
+        Self {
+            header: Header::new(class, true, Tag(tag), Length::Definite(0)),
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+
+    /// Parse a BER tagged value and apply the provided parsing function to content
+    ///
+    /// After parsing, the sequence object and header are discarded.
+    ///
+    /// Note: this function is provided for `Explicit`, but there is not difference between
+    /// explicit or implicit tags. The `op` function is responsible of handling the content.
+    #[inline]
+    pub fn from_ber_and_then<F>(
+        class: Class,
+        tag: u32,
+        bytes: &'a [u8],
+        op: F,
+    ) -> ParseResult<'a, T, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+        E: From<Error>,
+    {
+        Any::from_ber_and_then(class, tag, bytes, op)
+    }
+
+    /// Parse a DER tagged value and apply the provided parsing function to content
+    ///
+    /// After parsing, the sequence object and header are discarded.
+    ///
+    /// Note: this function is provided for `Explicit`, but there is not difference between
+    /// explicit or implicit tags. The `op` function is responsible of handling the content.
+    #[inline]
+    pub fn from_der_and_then<F>(
+        class: Class,
+        tag: u32,
+        bytes: &'a [u8],
+        op: F,
+    ) -> ParseResult<'a, T, E>
+    where
+        F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+        E: From<Error>,
+    {
+        Any::from_der_and_then(class, tag, bytes, op)
+    }
+}
+
+impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Explicit, T, E>
+where
+    T: FromBer<'a, E>,
+    E: From<Error>,
+{
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+        let header = any.header;
+        let (_, inner) = T::from_ber(any.data)?;
+        let tagged = TaggedParser {
+            header,
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        };
+        Ok((rem, tagged))
+    }
+}
+
+impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Explicit, T, E>
+where
+    T: FromDer<'a, E>,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        let header = any.header;
+        let (_, inner) = T::from_der(any.data)?;
+        let tagged = TaggedParser {
+            header,
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        };
+        Ok((rem, tagged))
+    }
+}
+
+impl<'a, T> CheckDerConstraints for TaggedParser<'a, Explicit, T>
+where
+    T: CheckDerConstraints,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length.assert_definite()?;
+        let (_, inner_any) = Any::from_der(any.data)?;
+        T::check_constraints(&inner_any)?;
+        Ok(())
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a, T> ToDer for TaggedParser<'a, Explicit, T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.inner.to_der_len()?;
+        if sz < 127 {
+            // 1 (class+tag) + 1 (length) + len
+            Ok(2 + sz)
+        } else {
+            // 1 (class+tag) + n (length) + len
+            let n = Length::Definite(sz).to_der_len()?;
+            Ok(1 + n + sz)
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let inner_len = self.inner.to_der_len()?;
+        let header = Header::new(self.class(), true, self.tag(), Length::Definite(inner_len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.inner.write_der(writer)
+    }
+}
diff --git a/src/asn1_types/tagged/helpers.rs b/src/asn1_types/tagged/helpers.rs
new file mode 100644
index 0000000..dfb1018
--- /dev/null
+++ b/src/asn1_types/tagged/helpers.rs
@@ -0,0 +1,103 @@
+use super::{Explicit, Implicit, TaggedParser};
+use crate::{Any, Error, FromDer, Header, ParseResult, Tag, Tagged};
+use nom::error::ParseError;
+use nom::{Err, IResult};
+
+// helper functions for parsing tagged objects
+
+pub fn parse_der_tagged_explicit<'a, IntoTag, T, E>(
+    tag: IntoTag,
+) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Explicit, T, E>, E>
+where
+    IntoTag: Into<Tag>,
+    TaggedParser<'a, Explicit, T, E>: FromDer<'a, E>,
+    E: From<Error>,
+{
+    let tag = tag.into();
+    move |i| {
+        let (rem, tagged) = TaggedParser::from_der(i)?;
+        tagged.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+        Ok((rem, tagged))
+    }
+}
+
+pub fn parse_der_tagged_explicit_g<'a, IntoTag, T, F, E>(
+    tag: IntoTag,
+    f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+    F: Fn(&'a [u8], Header<'a>) -> IResult<&'a [u8], T, E>,
+    E: ParseError<&'a [u8]> + From<Error>,
+    IntoTag: Into<Tag>,
+{
+    let tag = tag.into();
+    parse_der_container(tag, move |any: Any<'a>| {
+        any.header
+            .assert_tag(tag)
+            .map_err(|e| nom::Err::convert(e.into()))?;
+        f(any.data, any.header)
+    })
+}
+
+pub fn parse_der_tagged_implicit<'a, IntoTag, T, E>(
+    tag: IntoTag,
+) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Implicit, T, E>, E>
+where
+    IntoTag: Into<Tag>,
+    // T: TryFrom<Any<'a>, Error = Error> + Tagged,
+    TaggedParser<'a, Implicit, T, E>: FromDer<'a, E>,
+    E: From<Error>,
+{
+    let tag = tag.into();
+    move |i| {
+        let (rem, tagged) = TaggedParser::from_der(i)?;
+        tagged
+            .assert_tag(tag)
+            .map_err(|e| nom::Err::convert(e.into()))?;
+        Ok((rem, tagged))
+    }
+}
+
+pub fn parse_der_tagged_implicit_g<'a, IntoTag, T, F, E>(
+    tag: IntoTag,
+    f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+    F: Fn(&'a [u8], Tag, Header<'a>) -> IResult<&'a [u8], T, E>,
+    E: ParseError<&'a [u8]> + From<Error>,
+    IntoTag: Into<Tag>,
+    T: Tagged,
+{
+    let tag = tag.into();
+    parse_der_container(tag, move |any: Any<'a>| {
+        // verify tag of external header
+        any.header
+            .assert_tag(tag)
+            .map_err(|e| nom::Err::convert(e.into()))?;
+        // build a fake header with the expected tag
+        let Any { header, data } = any;
+        let header = Header {
+            tag: T::TAG,
+            ..header.clone()
+        };
+        f(data, tag, header)
+    })
+}
+
+fn parse_der_container<'a, T, F, E>(
+    tag: Tag,
+    f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+    F: Fn(Any<'a>) -> IResult<&'a [u8], T, E>,
+    E: ParseError<&'a [u8]> + From<Error>,
+{
+    move |i: &[u8]| {
+        let (rem, any) = Any::from_der(i).map_err(nom::Err::convert)?;
+        any.header
+            .assert_tag(tag)
+            .map_err(|e| nom::Err::convert(e.into()))?;
+        let (_, output) = f(any)?;
+        Ok((rem, output))
+    }
+}
diff --git a/src/asn1_types/tagged/implicit.rs b/src/asn1_types/tagged/implicit.rs
new file mode 100644
index 0000000..53a921c
--- /dev/null
+++ b/src/asn1_types/tagged/implicit.rs
@@ -0,0 +1,287 @@
+use crate::*;
+use core::convert::TryFrom;
+use core::marker::PhantomData;
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>>
+    for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: Tagged,
+    E: From<Error>,
+{
+    type Error = E;
+
+    fn try_from(any: Any<'a>) -> Result<Self, E> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b, E, T, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>>
+    for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: Tagged,
+    E: From<Error>,
+{
+    type Error = E;
+
+    fn try_from(any: &'b Any<'a>) -> Result<Self, E> {
+        any.tag().assert_eq(Tag(TAG))?;
+        // XXX if input is empty, this function is not called
+
+        if any.class() as u8 != CLASS {
+            let class = Class::try_from(CLASS).ok();
+            return Err(Error::unexpected_class(class, any.class()).into());
+        }
+        let any = Any {
+            header: Header {
+                tag: T::TAG,
+                ..any.header.clone()
+            },
+            data: any.data,
+        };
+        match T::try_from(any) {
+            Ok(inner) => Ok(TaggedValue::implicit(inner)),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E>
+    for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: Tagged,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        any.tag()
+            .assert_eq(Tag(TAG))
+            .map_err(|e| Err::Error(e.into()))?;
+        if any.class() as u8 != CLASS {
+            let class = Class::try_from(CLASS).ok();
+            return Err(Err::Error(
+                Error::unexpected_class(class, any.class()).into(),
+            ));
+        }
+        let any = Any {
+            header: Header {
+                tag: T::TAG,
+                ..any.header.clone()
+            },
+            data: any.data,
+        };
+        match T::try_from(any) {
+            Ok(inner) => Ok((rem, TaggedValue::implicit(inner))),
+            Err(e) => Err(nom::Err::Error(e)),
+        }
+    }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints
+    for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+    T: CheckDerConstraints,
+    T: Tagged,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length.assert_definite()?;
+        let header = any.header.clone().with_tag(T::TAG);
+        let inner = Any::new(header, any.data);
+        T::check_constraints(&inner)?;
+        Ok(())
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        self.inner.to_der_len()
+    }
+
+    fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let class =
+            Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+        let mut v = Vec::new();
+        let inner_len = self.inner.write_der_content(&mut v)?;
+        // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+        // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+        let constructed = matches!(TAG, 16 | 17);
+        let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len));
+        let sz = header.write_der_header(writer)?;
+        let sz = sz + writer.write(&v)?;
+        Ok(sz)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut sink = std::io::sink();
+        let class =
+            Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+        let inner_len = self.inner.write_der_content(&mut sink)?;
+        // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+        // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+        let constructed = matches!(TAG, 16 | 17);
+        let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.inner.write_der(writer)
+    }
+}
+
+/// A helper object to parse `[ n ] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed.
+///
+/// # Examples
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, TaggedImplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = TaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2)));
+/// ```
+pub type TaggedImplicit<T, E, const TAG: u32> = TaggedValue<T, E, Implicit, CONTEXT_SPECIFIC, TAG>;
+
+impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Implicit, T, E>
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: Tagged,
+    E: From<Error>,
+{
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+        let Any { header, data } = any;
+        let any = Any {
+            header: Header {
+                tag: T::TAG,
+                ..header.clone()
+            },
+            data,
+        };
+        match T::try_from(any) {
+            Ok(t) => {
+                let tagged_value = TaggedParser {
+                    header,
+                    inner: t,
+                    tag_kind: PhantomData,
+                    _e: PhantomData,
+                };
+                Ok((rem, tagged_value))
+            }
+            Err(e) => Err(nom::Err::Error(e)),
+        }
+    }
+}
+
+// implementations for TaggedParser
+
+impl<'a, T, E> TaggedParser<'a, Implicit, T, E> {
+    pub const fn new_implicit(class: Class, constructed: bool, tag: u32, inner: T) -> Self {
+        Self {
+            header: Header::new(class, constructed, Tag(tag), Length::Definite(0)),
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+}
+
+impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Implicit, T, E>
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: CheckDerConstraints,
+    T: Tagged,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        let Any { header, data } = any;
+        let any = Any {
+            header: Header {
+                tag: T::TAG,
+                ..header.clone()
+            },
+            data,
+        };
+        T::check_constraints(&any).map_err(|e| nom::Err::Error(e.into()))?;
+        match T::try_from(any) {
+            Ok(t) => {
+                let tagged_value = TaggedParser {
+                    header,
+                    inner: t,
+                    tag_kind: PhantomData,
+                    _e: PhantomData,
+                };
+                Ok((rem, tagged_value))
+            }
+            Err(e) => Err(nom::Err::Error(e)),
+        }
+    }
+}
+
+impl<'a, T> CheckDerConstraints for TaggedParser<'a, Implicit, T>
+where
+    T: CheckDerConstraints,
+    T: Tagged,
+{
+    fn check_constraints(any: &Any) -> Result<()> {
+        any.header.length.assert_definite()?;
+        let any = Any {
+            header: Header {
+                tag: T::TAG,
+                ..any.header.clone()
+            },
+            data: any.data,
+        };
+        T::check_constraints(&any)?;
+        Ok(())
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a, T> ToDer for TaggedParser<'a, Implicit, T>
+where
+    T: ToDer,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        self.inner.to_der_len()
+    }
+
+    fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut v = Vec::new();
+        let inner_len = self.inner.write_der_content(&mut v)?;
+        // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+        // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+        let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len));
+        let sz = header.write_der_header(writer)?;
+        let sz = sz + writer.write(&v)?;
+        Ok(sz)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let mut sink = std::io::sink();
+        let inner_len = self.inner.write_der_content(&mut sink)?;
+        // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+        // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+        let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        self.inner.write_der_content(writer)
+    }
+}
diff --git a/src/asn1_types/tagged/optional.rs b/src/asn1_types/tagged/optional.rs
new file mode 100644
index 0000000..00504df
--- /dev/null
+++ b/src/asn1_types/tagged/optional.rs
@@ -0,0 +1,240 @@
+use super::{explicit::TaggedExplicit, implicit::TaggedImplicit};
+use crate::*;
+
+/// Helper object to parse TAGGED OPTIONAL types (explicit or implicit)
+///
+/// This object can be used similarly to a builder pattern, to specify the expected class and
+/// tag of the object to parse, and the content parsing function.
+///
+/// The content parsing function takes two arguments: the outer header, and the data.
+///
+/// It can be used for both EXPLICIT or IMPLICIT tagged objects by using parsing functions that
+/// expect a header (or not) in the contents.
+///
+/// The [`OptTaggedParser::from`] method is a shortcut to build an object with `ContextSpecific`
+/// class and the given tag. The [`OptTaggedParser::new`] method is more generic.
+///
+/// See also [`OptTaggedExplicit`] and [`OptTaggedImplicit`] for alternatives that implement [`FromBer`]/
+/// [`FromDer`].
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Class, FromDer, Integer, Tag, OptTaggedParser};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedParser::new(Class::Application, Tag(0))
+///                     .parse_der(bytes, |_, data| Integer::from_der(data))
+///                     .unwrap();
+///
+/// assert_eq!(tagged, Some(Integer::from(2)));
+/// ```
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, Integer, OptTaggedParser};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedParser::from(0)
+///                     .parse_der::<_, Error, _>(bytes, |_, data| Ok((&[], Integer::new(data))))
+///                     .unwrap();
+///
+/// assert_eq!(tagged, Some(Integer::from(2)));
+/// ```
+#[derive(Debug)]
+pub struct OptTaggedParser {
+    /// The expected class for the object to parse
+    pub class: Class,
+    /// The expected tag for the object to parse
+    pub tag: Tag,
+}
+
+impl OptTaggedParser {
+    /// Build a new `OptTaggedParser` object.
+    ///
+    /// If using `Class::ContextSpecific`, using [`OptTaggedParser::from`] with either a `Tag` or `u32` is
+    /// a shorter way to build this object.
+    pub const fn new(class: Class, tag: Tag) -> Self {
+        OptTaggedParser { class, tag }
+    }
+
+    pub const fn universal(tag: u32) -> Self {
+        Self::new(Class::Universal, Tag(tag))
+    }
+
+    pub const fn tagged(tag: u32) -> Self {
+        Self::new(Class::ContextSpecific, Tag(tag))
+    }
+
+    pub const fn application(tag: u32) -> Self {
+        Self::new(Class::Application, Tag(tag))
+    }
+
+    pub const fn private(tag: u32) -> Self {
+        Self::new(Class::Private, Tag(tag))
+    }
+
+    /// Parse input as BER, and apply the provided function to parse object.
+    ///
+    /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`.
+    ///
+    ///  This function returns an error if tag was found but has a different class, or if parsing fails.
+    ///
+    /// # Examples
+    ///
+    /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+    ///
+    /// ```rust
+    /// use asn1_rs::{FromBer, Integer, OptTaggedParser};
+    ///
+    /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+    ///
+    /// let (_, tagged) = OptTaggedParser::from(0)
+    ///                     .parse_ber(bytes, |_, data| Integer::from_ber(data))
+    ///                     .unwrap();
+    ///
+    /// assert_eq!(tagged, Some(Integer::from(2)));
+    /// ```
+    pub fn parse_ber<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E>
+    where
+        F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>,
+        E: From<Error>,
+    {
+        if bytes.is_empty() {
+            return Ok((bytes, None));
+        }
+        let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+        if any.tag() != self.tag {
+            return Ok((bytes, None));
+        }
+        if any.class() != self.class {
+            return Err(Err::Error(
+                Error::unexpected_class(Some(self.class), any.class()).into(),
+            ));
+        }
+        let Any { header, data } = any;
+        let (_, res) = f(header, data)?;
+        Ok((rem, Some(res)))
+    }
+
+    /// Parse input as DER, and apply the provided function to parse object.
+    ///
+    /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`.
+    ///
+    ///  This function returns an error if tag was found but has a different class, or if parsing fails.
+    ///
+    /// # Examples
+    ///
+    /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+    ///
+    /// ```rust
+    /// use asn1_rs::{FromDer, Integer, OptTaggedParser};
+    ///
+    /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+    ///
+    /// let (_, tagged) = OptTaggedParser::from(0)
+    ///                     .parse_der(bytes, |_, data| Integer::from_der(data))
+    ///                     .unwrap();
+    ///
+    /// assert_eq!(tagged, Some(Integer::from(2)));
+    /// ```
+    pub fn parse_der<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E>
+    where
+        F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>,
+        E: From<Error>,
+    {
+        if bytes.is_empty() {
+            return Ok((bytes, None));
+        }
+        let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+        if any.tag() != self.tag {
+            return Ok((bytes, None));
+        }
+        if any.class() != self.class {
+            return Err(Err::Error(
+                Error::unexpected_class(Some(self.class), any.class()).into(),
+            ));
+        }
+        let Any { header, data } = any;
+        let (_, res) = f(header, data)?;
+        Ok((rem, Some(res)))
+    }
+}
+
+impl From<Tag> for OptTaggedParser {
+    /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag
+    #[inline]
+    fn from(tag: Tag) -> Self {
+        OptTaggedParser::new(Class::ContextSpecific, tag)
+    }
+}
+
+impl From<u32> for OptTaggedParser {
+    /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag
+    #[inline]
+    fn from(tag: u32) -> Self {
+        OptTaggedParser::new(Class::ContextSpecific, Tag(tag))
+    }
+}
+
+/// A helper object to parse `[ n ] EXPLICIT T OPTIONAL`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation.
+///
+/// # Examples
+///
+/// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, OptTaggedExplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, Some(TaggedValue::explicit(Integer::from(2))));
+///
+/// // If tagged object is not present or has different tag, parsing
+/// // also succeeds (returning None):
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(&[]).unwrap();
+/// assert_eq!(tagged, None);
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 1>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, None);
+/// ```
+pub type OptTaggedExplicit<T, E, const TAG: u32> = Option<TaggedExplicit<T, E, TAG>>;
+
+/// A helper object to parse `[ n ] IMPLICIT T OPTIONAL`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation.
+///
+/// # Examples
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, OptTaggedImplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, Some(TaggedValue::implicit(Integer::from(2))));
+///
+/// // If tagged object is not present or has different tag, parsing
+/// // also succeeds (returning None):
+/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(&[]).unwrap();
+/// assert_eq!(tagged, None);
+/// ```
+pub type OptTaggedImplicit<T, E, const TAG: u32> = Option<TaggedImplicit<T, E, TAG>>;
diff --git a/src/asn1_types/tagged/parser.rs b/src/asn1_types/tagged/parser.rs
new file mode 100644
index 0000000..243b081
--- /dev/null
+++ b/src/asn1_types/tagged/parser.rs
@@ -0,0 +1,78 @@
+use crate::*;
+use core::marker::PhantomData;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct TaggedParser<'a, TagKind, T, E = Error> {
+    pub header: Header<'a>,
+    pub inner: T,
+
+    pub(crate) tag_kind: PhantomData<TagKind>,
+    pub(crate) _e: PhantomData<E>,
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E> {
+    pub const fn new(header: Header<'a>, inner: T) -> Self {
+        TaggedParser {
+            header,
+            inner,
+            tag_kind: PhantomData,
+            _e: PhantomData,
+        }
+    }
+
+    pub const fn assert_class(&self, class: Class) -> Result<()> {
+        self.header.assert_class(class)
+    }
+
+    pub const fn assert_tag(&self, tag: Tag) -> Result<()> {
+        self.header.assert_tag(tag)
+    }
+
+    #[inline]
+    pub const fn class(&self) -> Class {
+        self.header.class
+    }
+
+    #[inline]
+    pub const fn tag(&self) -> Tag {
+        self.header.tag
+    }
+}
+
+impl<'a, TagKind, T, E> AsRef<T> for TaggedParser<'a, TagKind, T, E> {
+    fn as_ref(&self) -> &T {
+        &self.inner
+    }
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E>
+where
+    Self: FromBer<'a, E>,
+    E: From<Error>,
+{
+    pub fn parse_ber(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, t) = TaggedParser::<TagKind, T, E>::from_ber(bytes)?;
+        t.assert_class(class).map_err(|e| Err::Error(e.into()))?;
+        t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+        Ok((rem, t))
+    }
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E>
+where
+    Self: FromDer<'a, E>,
+    E: From<Error>,
+{
+    pub fn parse_der(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+        let (rem, t) = TaggedParser::<TagKind, T, E>::from_der(bytes)?;
+        t.assert_class(class).map_err(|e| Err::Error(e.into()))?;
+        t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+        Ok((rem, t))
+    }
+}
+
+impl<'a, TagKind, T, E> DynTagged for TaggedParser<'a, TagKind, T, E> {
+    fn tag(&self) -> Tag {
+        self.tag()
+    }
+}
diff --git a/src/asn1_types/tagged/private.rs b/src/asn1_types/tagged/private.rs
new file mode 100644
index 0000000..5d8708a
--- /dev/null
+++ b/src/asn1_types/tagged/private.rs
@@ -0,0 +1,42 @@
+use crate::{Class, Explicit, Implicit, TaggedValue};
+
+/// A helper object to parse `[PRIVATE n] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit private-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[PRIVATE 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, PrivateExplicit, TaggedValue};
+///
+/// let bytes = &[0xe0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = PrivateExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type PrivateExplicit<T, E, const TAG: u32> =
+    TaggedValue<T, E, Explicit, { Class::PRIVATE }, TAG>;
+
+/// A helper object to parse `[PRIVATE n] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse implicit private-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[PRIVATE 0] IMPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, PrivateImplicit, TaggedValue};
+///
+/// let bytes = &[0xe0, 0x1, 0x2];
+///
+/// let (_, tagged) = PrivateImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8)));
+/// ```
+pub type PrivateImplicit<T, E, const TAG: u32> =
+    TaggedValue<T, E, Implicit, { Class::PRIVATE }, TAG>;
diff --git a/src/asn1_types/utctime.rs b/src/asn1_types/utctime.rs
new file mode 100644
index 0000000..8604914
--- /dev/null
+++ b/src/asn1_types/utctime.rs
@@ -0,0 +1,222 @@
+use crate::datetime::decode_decimal;
+use crate::*;
+use core::convert::TryFrom;
+use core::fmt;
+#[cfg(feature = "datetime")]
+use time::OffsetDateTime;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct UtcTime(pub ASN1DateTime);
+
+impl UtcTime {
+    pub const fn new(datetime: ASN1DateTime) -> Self {
+        UtcTime(datetime)
+    }
+
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
+        // X.680 section 43 defines a UniversalTime as a VisibleString restricted to:
+        //
+        // a) the six digits YYMMDD where YY is the two low-order digits of the Christian year, MM is the month
+        // (counting January as 01), and DD is the day of the month (01 to 31); and
+        // b) either:
+        //   1) the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); or
+        //   2) the six digits hhmmss where hh and mm are as in 1) above, and ss is seconds (00 to 59); and
+        // c) either:
+        //   1) the character Z ; or
+        //   2) one of the characters + or - , followed by hhmm, where hh is hour and mm is minutes.
+        //
+        // XXX // RFC 5280 requires mandatory seconds and Z-normalized time zone
+        let (year, month, day, hour, minute, rem) = match bytes {
+            [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => {
+                let year = decode_decimal(Self::TAG, *year1, *year2)?;
+                let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
+                let day = decode_decimal(Self::TAG, *day1, *day2)?;
+                let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
+                let minute = decode_decimal(Self::TAG, *min1, *min2)?;
+                (year, month, day, hour, minute, rem)
+            }
+            _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
+        };
+        if rem.is_empty() {
+            return Err(Self::TAG.invalid_value("malformed time string"));
+        }
+        // check for seconds
+        let (second, rem) = match rem {
+            [sec1, sec2, rem @ ..] => {
+                let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
+                (second, rem)
+            }
+            _ => (0, rem),
+        };
+        if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
+            return Err(Self::TAG.invalid_value("time components with invalid values"));
+        }
+        if rem.is_empty() {
+            return Err(Self::TAG.invalid_value("malformed time string"));
+        }
+        let tz = match rem {
+            [b'Z'] => ASN1TimeZone::Z,
+            [b'+', h1, h2, m1, m2] => {
+                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+                ASN1TimeZone::Offset(hh as i8, mm as i8)
+            }
+            [b'-', h1, h2, m1, m2] => {
+                let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+                let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+                ASN1TimeZone::Offset(-(hh as i8), mm as i8)
+            }
+            _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
+        };
+        Ok(UtcTime(ASN1DateTime::new(
+            year as u32,
+            month,
+            day,
+            hour,
+            minute,
+            second,
+            None,
+            tz,
+        )))
+        // match *bytes {
+        //     [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
+        //         let year = decode_decimal(Self::TAG, year1, year2)?;
+        //         let month = decode_decimal(Self::TAG, mon1, mon2)?;
+        //         let day = decode_decimal(Self::TAG, day1, day2)?;
+        //         let hour = decode_decimal(Self::TAG, hour1, hour2)?;
+        //         let minute = decode_decimal(Self::TAG, min1, min2)?;
+        //         let second = decode_decimal(Self::TAG, sec1, sec2)?;
+
+        //         // RFC 5280 rules for interpreting the year
+        //         let year = if year >= 50 { year + 1900 } else { year + 2000 };
+
+        //         Ok(UtcTime::new(year, month, day, hour, minute, second))
+        //     }
+        //     _ => Err(Error::InvalidValue),
+        // }
+    }
+
+    /// Return a ISO 8601 combined date and time with time zone.
+    #[cfg(feature = "datetime")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+    #[inline]
+    pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
+        self.0.to_datetime()
+    }
+
+    /// Return an adjusted ISO 8601 combined date and time with time zone.
+    /// According to Universal time definition in X.680 we add 2000 years
+    /// from 0 to 49 year and 1900 otherwise.
+    #[cfg(feature = "datetime")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+    #[inline]
+    pub fn utc_adjusted_datetime(&self) -> Result<OffsetDateTime> {
+        self.0.to_datetime().and_then(|dt| {
+            let year = dt.year();
+            // We follow the Universal time definition in X.680 for interpreting
+            // the adjusted year
+            let year = if year >= 50 { year + 1900 } else { year + 2000 };
+            time::Date::from_calendar_date(year, dt.month(), dt.day())
+                .map(|d| dt.replace_date(d))
+                .map_err(|_e| Self::TAG.invalid_value("Invalid adjusted date"))
+        })
+    }
+
+    /// Returns the number of non-leap seconds since the midnight on January 1, 1970.
+    #[cfg(feature = "datetime")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+    pub fn timestamp(&self) -> Result<i64> {
+        let dt = self.0.to_datetime()?;
+        Ok(dt.unix_timestamp())
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for UtcTime {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<UtcTime> {
+        TryFrom::try_from(&any)
+    }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for UtcTime {
+    type Error = Error;
+
+    fn try_from(any: &'b Any<'a>) -> Result<UtcTime> {
+        any.tag().assert_eq(Self::TAG)?;
+        #[allow(clippy::trivially_copy_pass_by_ref)]
+        fn is_visible(b: &u8) -> bool {
+            0x20 <= *b && *b <= 0x7f
+        }
+        if !any.data.iter().all(is_visible) {
+            return Err(Error::StringInvalidCharset);
+        }
+
+        UtcTime::from_bytes(any.data)
+    }
+}
+
+impl fmt::Display for UtcTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let dt = &self.0;
+        match dt.tz {
+            ASN1TimeZone::Z | ASN1TimeZone::Undefined => write!(
+                f,
+                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}Z",
+                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
+            ),
+            ASN1TimeZone::Offset(hh, mm) => {
+                let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
+                write!(
+                    f,
+                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{:02}{:02}",
+                    dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, s, hh, mm
+                )
+            }
+        }
+    }
+}
+
+impl CheckDerConstraints for UtcTime {
+    fn check_constraints(_any: &Any) -> Result<()> {
+        Ok(())
+    }
+}
+
+impl DerAutoDerive for UtcTime {}
+
+impl Tagged for UtcTime {
+    const TAG: Tag = Tag::UtcTime;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for UtcTime {
+    fn to_der_len(&self) -> Result<usize> {
+        // data:
+        // - 6 bytes for YYMMDD
+        // - 6 for hhmmss in DER (X.690 section 11.8.2)
+        // - 1 for the character Z in DER (X.690 section 11.8.1)
+        // data length: 13
+        //
+        // thus, length will always be on 1 byte (short length) and
+        // class+structure+tag also on 1
+        //
+        // total: 15 = 1 (class+constructed+tag) + 1 (length) + 13
+        Ok(15)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // see above for length value
+        writer.write(&[Self::TAG.0 as u8, 13]).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        write!(
+            writer,
+            "{:02}{:02}{:02}{:02}{:02}{:02}Z",
+            self.0.year, self.0.month, self.0.day, self.0.hour, self.0.minute, self.0.second,
+        )?;
+        // write_fmt returns (), see above for length value
+        Ok(13)
+    }
+}
diff --git a/src/ber/mod.rs b/src/ber/mod.rs
new file mode 100644
index 0000000..5feedaf
--- /dev/null
+++ b/src/ber/mod.rs
@@ -0,0 +1,3 @@
+mod parser;
+
+pub use parser::*;
diff --git a/src/ber/parser.rs b/src/ber/parser.rs
new file mode 100644
index 0000000..fdfdb88
--- /dev/null
+++ b/src/ber/parser.rs
@@ -0,0 +1,169 @@
+use crate::error::*;
+use crate::header::*;
+use crate::{BerParser, DerParser, FromBer, Length, Tag};
+use nom::bytes::streaming::take;
+use nom::{Err, Needed, Offset};
+use rusticata_macros::custom_check;
+
+/// Default maximum recursion limit
+pub const MAX_RECURSION: usize = 50;
+
+/// Default maximum object size (2^32)
+// pub const MAX_OBJECT_SIZE: usize = 4_294_967_295;
+
+pub trait GetObjectContent {
+    /// Return the raw content (bytes) of the next ASN.1 encoded object
+    ///
+    /// Note: if using BER and length is indefinite, terminating End-Of-Content is NOT included
+    fn get_object_content<'a>(
+        i: &'a [u8],
+        hdr: &'_ Header,
+        max_depth: usize,
+    ) -> ParseResult<'a, &'a [u8]>;
+}
+
+impl GetObjectContent for BerParser {
+    fn get_object_content<'a>(
+        i: &'a [u8],
+        hdr: &'_ Header,
+        max_depth: usize,
+    ) -> ParseResult<'a, &'a [u8]> {
+        let start_i = i;
+        let (i, _) = ber_skip_object_content(i, hdr, max_depth)?;
+        let len = start_i.offset(i);
+        let (content, i) = start_i.split_at(len);
+        // if len is indefinite, there are 2 extra bytes for EOC
+        if hdr.length == Length::Indefinite {
+            let len = content.len();
+            assert!(len >= 2);
+            Ok((i, &content[..len - 2]))
+        } else {
+            Ok((i, content))
+        }
+    }
+}
+
+impl GetObjectContent for DerParser {
+    /// Skip object content, accepting only DER
+    ///
+    /// This this function is for DER only, it cannot go into recursion (no indefinite length)
+    fn get_object_content<'a>(
+        i: &'a [u8],
+        hdr: &'_ Header,
+        _max_depth: usize,
+    ) -> ParseResult<'a, &'a [u8]> {
+        match hdr.length {
+            Length::Definite(l) => take(l)(i),
+            Length::Indefinite => Err(Err::Error(Error::DerConstraintFailed(
+                DerConstraint::IndefiniteLength,
+            ))),
+        }
+    }
+}
+
+/// Skip object content, and return true if object was End-Of-Content
+fn ber_skip_object_content<'a>(
+    i: &'a [u8],
+    hdr: &Header,
+    max_depth: usize,
+) -> ParseResult<'a, bool> {
+    if max_depth == 0 {
+        return Err(Err::Error(Error::BerMaxDepth));
+    }
+    match hdr.length {
+        Length::Definite(l) => {
+            if l == 0 && hdr.tag == Tag::EndOfContent {
+                return Ok((i, true));
+            }
+            let (i, _) = take(l)(i)?;
+            Ok((i, false))
+        }
+        Length::Indefinite => {
+            hdr.assert_constructed()?;
+            // read objects until EndOfContent (00 00)
+            // this is recursive
+            let mut i = i;
+            loop {
+                let (i2, header2) = Header::from_ber(i)?;
+                let (i3, eoc) = ber_skip_object_content(i2, &header2, max_depth - 1)?;
+                if eoc {
+                    // return false, since top object was not EndOfContent
+                    return Ok((i3, false));
+                }
+                i = i3;
+            }
+        }
+    }
+}
+
+/// Try to parse input bytes as u64
+#[inline]
+pub(crate) fn bytes_to_u64(s: &[u8]) -> core::result::Result<u64, Error> {
+    let mut u: u64 = 0;
+    for &c in s {
+        if u & 0xff00_0000_0000_0000 != 0 {
+            return Err(Error::IntegerTooLarge);
+        }
+        u <<= 8;
+        u |= u64::from(c);
+    }
+    Ok(u)
+}
+
+pub(crate) fn parse_identifier(i: &[u8]) -> ParseResult<(u8, u8, u32, &[u8])> {
+    if i.is_empty() {
+        Err(Err::Incomplete(Needed::new(1)))
+    } else {
+        let a = i[0] >> 6;
+        let b = u8::from(i[0] & 0b0010_0000 != 0);
+        let mut c = u32::from(i[0] & 0b0001_1111);
+
+        let mut tag_byte_count = 1;
+
+        if c == 0x1f {
+            c = 0;
+            loop {
+                // Make sure we don't read past the end of our data.
+                custom_check!(i, tag_byte_count >= i.len(), Error::InvalidTag)?;
+
+                // With tag defined as u32 the most we can fit in is four tag bytes.
+                // (X.690 doesn't actually specify maximum tag width.)
+                custom_check!(i, tag_byte_count > 5, Error::InvalidTag)?;
+
+                c = (c << 7) | (u32::from(i[tag_byte_count]) & 0x7f);
+                let done = i[tag_byte_count] & 0x80 == 0;
+                tag_byte_count += 1;
+                if done {
+                    break;
+                }
+            }
+        }
+
+        let (raw_tag, rem) = i.split_at(tag_byte_count);
+
+        Ok((rem, (a, b, c, raw_tag)))
+    }
+}
+
+/// Return the MSB and the rest of the first byte, or an error
+pub(crate) fn parse_ber_length_byte(i: &[u8]) -> ParseResult<(u8, u8)> {
+    if i.is_empty() {
+        Err(Err::Incomplete(Needed::new(1)))
+    } else {
+        let a = i[0] >> 7;
+        let b = i[0] & 0b0111_1111;
+        Ok((&i[1..], (a, b)))
+    }
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! der_constraint_fail_if(
+    ($slice:expr, $cond:expr, $constraint:expr) => (
+        {
+            if $cond {
+                return Err(::nom::Err::Error(Error::DerConstraintFailed($constraint)));
+            }
+        }
+    );
+);
diff --git a/src/class.rs b/src/class.rs
new file mode 100644
index 0000000..9a4f9de
--- /dev/null
+++ b/src/class.rs
@@ -0,0 +1,94 @@
+use core::convert::TryFrom;
+use core::fmt;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct BerClassFromIntError(pub(crate) ());
+
+/// BER Object class of tag
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u8)]
+pub enum Class {
+    /// `Universal` class of tags (`0b00`)
+    Universal = 0b00,
+    /// `Application` class of tags (`0b01`)
+    Application = 0b01,
+    /// `Context-Specific` class of tags (`0b10`)
+    ContextSpecific = 0b10,
+    /// `Private` class of tags (`0b11`)
+    Private = 0b11,
+}
+
+impl Class {
+    /// `Universal` class of tags (`0b00`)
+    pub const UNIVERSAL: u8 = 0b00;
+    /// `Application` class of tags (`0b01`)
+    pub const APPLICATION: u8 = 0b01;
+    /// `Context-Specific` class of tags (`0b10`)
+    pub const CONTEXT_SPECIFIC: u8 = 0b10;
+    /// `Private` class of tags (`0b11`)
+    pub const PRIVATE: u8 = 0b11;
+
+    pub const fn assert_eq(&self, class: Class) -> Result<(), crate::error::Error> {
+        if *self as u8 == class as u8 {
+            Ok(())
+        } else {
+            Err(crate::error::Error::UnexpectedClass {
+                expected: Some(class),
+                actual: *self,
+            })
+        }
+    }
+}
+
+impl fmt::Display for Class {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let s = match self {
+            Class::Universal => "UNIVERSAL",
+            Class::Application => "APPLICATION",
+            Class::ContextSpecific => "CONTEXT-SPECIFIC",
+            Class::Private => "PRIVATE",
+        };
+        write!(f, "{}", s)
+    }
+}
+
+impl TryFrom<u8> for Class {
+    type Error = BerClassFromIntError;
+
+    #[inline]
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0b00 => Ok(Class::Universal),
+            0b01 => Ok(Class::Application),
+            0b10 => Ok(Class::ContextSpecific),
+            0b11 => Ok(Class::Private),
+            _ => Err(BerClassFromIntError(())),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn methods_class() {
+        let c = Class::Universal;
+        assert!(c.assert_eq(Class::Universal).is_ok());
+        assert!(c.assert_eq(Class::Private).is_err());
+
+        assert_eq!(Class::Universal.to_string().as_str(), "UNIVERSAL");
+        assert_eq!(Class::Application.to_string().as_str(), "APPLICATION");
+        assert_eq!(
+            Class::ContextSpecific.to_string().as_str(),
+            "CONTEXT-SPECIFIC"
+        );
+        assert_eq!(Class::Private.to_string().as_str(), "PRIVATE");
+
+        assert!(Class::try_from(0b00).is_ok());
+        assert!(Class::try_from(0b01).is_ok());
+        assert!(Class::try_from(0b10).is_ok());
+        assert!(Class::try_from(0b11).is_ok());
+        assert!(Class::try_from(4).is_err());
+    }
+}
diff --git a/src/const_int.rs b/src/const_int.rs
new file mode 100644
index 0000000..75beeaa
--- /dev/null
+++ b/src/const_int.rs
@@ -0,0 +1,43 @@
+use crate::{Tag, Tagged};
+
+#[derive(Debug)]
+pub struct ConstInt {
+    buffer: [u8; 10],
+    n: usize,
+}
+
+// XXX only ToBer/ToDer trait supported?
+
+impl Tagged for ConstInt {
+    const TAG: Tag = Tag::Integer;
+}
+
+#[derive(Debug)]
+pub struct IntBuilder {}
+
+impl IntBuilder {
+    pub const fn build(&self, i: u64) -> ConstInt {
+        let b = i.to_be_bytes();
+        let mut out = [0u8; 10];
+        out[0] = 0x4;
+        let src_len = b.len();
+        let mut src_index = 0;
+        while src_index < src_len && b[src_index] == 0 {
+            src_index += 1;
+        }
+        out[1] = (src_len - src_index) as u8;
+        let mut dst_index = 2;
+        while src_index < src_len {
+            out[dst_index] = b[src_index];
+            src_index += 1;
+            dst_index += 1;
+        }
+        // XXX will not work: we need to allocate a Vec
+        // also, we cannot just store the bytes (there are extra zeroes at end)
+        // Integer::new(&out[..dst_index])
+        ConstInt {
+            buffer: out,
+            n: dst_index,
+        }
+    }
+}
diff --git a/src/datetime.rs b/src/datetime.rs
new file mode 100644
index 0000000..613b44b
--- /dev/null
+++ b/src/datetime.rs
@@ -0,0 +1,108 @@
+use crate::{Result, Tag};
+use alloc::format;
+use alloc::string::ToString;
+use core::fmt;
+#[cfg(feature = "datetime")]
+use time::OffsetDateTime;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
+pub enum ASN1TimeZone {
+    /// No timezone provided
+    Undefined,
+    /// Coordinated universal time
+    Z,
+    /// Local zone, with offset to coordinated universal time
+    ///
+    /// `(offset_hour, offset_minute)`
+    Offset(i8, i8),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
+pub struct ASN1DateTime {
+    pub year: u32,
+    pub month: u8,
+    pub day: u8,
+    pub hour: u8,
+    pub minute: u8,
+    pub second: u8,
+    pub millisecond: Option<u16>,
+    pub tz: ASN1TimeZone,
+}
+
+impl ASN1DateTime {
+    #[allow(clippy::too_many_arguments)]
+    pub const fn new(
+        year: u32,
+        month: u8,
+        day: u8,
+        hour: u8,
+        minute: u8,
+        second: u8,
+        millisecond: Option<u16>,
+        tz: ASN1TimeZone,
+    ) -> Self {
+        ASN1DateTime {
+            year,
+            month,
+            day,
+            hour,
+            minute,
+            second,
+            millisecond,
+            tz,
+        }
+    }
+
+    #[cfg(feature = "datetime")]
+    fn to_time_datetime(
+        &self,
+    ) -> core::result::Result<OffsetDateTime, time::error::ComponentRange> {
+        use std::convert::TryFrom;
+        use time::{Date, Month, PrimitiveDateTime, Time, UtcOffset};
+
+        let month = Month::try_from(self.month)?;
+        let date = Date::from_calendar_date(self.year as i32, month, self.day)?;
+        let time = Time::from_hms_milli(
+            self.hour,
+            self.minute,
+            self.second,
+            self.millisecond.unwrap_or(0),
+        )?;
+        let primitive_date = PrimitiveDateTime::new(date, time);
+        let offset = match self.tz {
+            ASN1TimeZone::Offset(h, m) => UtcOffset::from_hms(h, m, 0)?,
+            ASN1TimeZone::Undefined | ASN1TimeZone::Z => UtcOffset::UTC,
+        };
+        Ok(primitive_date.assume_offset(offset))
+    }
+
+    #[cfg(feature = "datetime")]
+    pub fn to_datetime(&self) -> Result<OffsetDateTime> {
+        use crate::Error;
+
+        self.to_time_datetime().map_err(|_| Error::InvalidDateTime)
+    }
+}
+
+impl fmt::Display for ASN1DateTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let fractional = match self.millisecond {
+            None => "".to_string(),
+            Some(v) => format!(".{}", v),
+        };
+        write!(
+            f,
+            "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
+            self.year, self.month, self.day, self.hour, self.minute, self.second, fractional,
+        )
+    }
+}
+
+/// Decode 2-digit decimal value
+pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result<u8> {
+    if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) {
+        Ok((hi - b'0') * 10 + (lo - b'0'))
+    } else {
+        Err(tag.invalid_value("expected digit"))
+    }
+}
diff --git a/src/derive.rs b/src/derive.rs
new file mode 100644
index 0000000..faf862e
--- /dev/null
+++ b/src/derive.rs
@@ -0,0 +1,322 @@
+/// # BerSequence custom derive
+///
+/// `BerSequence` is a custom derive attribute, to derive a BER [`Sequence`](super::Sequence) parser automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///
+/// `DerSequence` implies `BerSequence`, and will conflict with this custom derive. Use `BerSequence` when you only want the
+/// above traits derived.
+///
+/// Parsers will be automatically derived from struct fields. Every field type must implement the [`FromBer`](super::FromBer) trait.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 structure:
+/// <pre>
+/// S ::= SEQUENCE {
+///     a INTEGER(0..2^32),
+///     b INTEGER(0..2^16),
+///     c INTEGER(0..2^16),
+/// }
+/// </pre>
+///
+/// Define a structure and add the `BerSequence` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerSequence)]
+/// struct S {
+///   a: u32,
+///   b: u16,
+///   c: u16
+/// }
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerSequence)]
+/// #[debug_derive]
+/// struct S {
+///   a: u32,
+/// }
+/// ```
+pub use asn1_rs_derive::BerSequence;
+
+/// # DerSequence custom derive
+///
+/// `DerSequence` is a custom derive attribute, to derive both BER and DER [`Sequence`](super::Sequence) parsers automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///   - [`CheckDerConstraints`](super::CheckDerConstraints)
+///   - [`FromDer`](super::FromDer)
+///
+/// `DerSequence` implies `BerSequence`, and will conflict with this custom derive.
+///
+/// Parsers will be automatically derived from struct fields. Every field type must implement the [`FromDer`](super::FromDer) trait.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 structure:
+/// <pre>
+/// S ::= SEQUENCE {
+///     a INTEGER(0..2^32),
+///     b INTEGER(0..2^16),
+///     c INTEGER(0..2^16),
+/// }
+/// </pre>
+///
+/// Define a structure and add the `DerSequence` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerSequence)]
+/// struct S {
+///   a: u32,
+///   b: u16,
+///   c: u16
+/// }
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerSequence)]
+/// #[debug_derive]
+/// struct S {
+///   a: u32,
+/// }
+/// ```
+pub use asn1_rs_derive::DerSequence;
+
+/// # BerSet custom derive
+///
+/// `BerSet` is a custom derive attribute, to derive a BER [`Set`](super::Set) parser automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///
+/// `DerSet` implies `BerSet`, and will conflict with this custom derive. Use `BerSet` when you only want the
+/// above traits derived.
+///
+/// Parsers will be automatically derived from struct fields. Every field type must implement the [`FromBer`](super::FromBer) trait.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 structure:
+/// <pre>
+/// S ::= SET {
+///     a INTEGER(0..2^32),
+///     b INTEGER(0..2^16),
+///     c INTEGER(0..2^16),
+/// }
+/// </pre>
+///
+/// Define a structure and add the `BerSet` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerSet)]
+/// struct S {
+///   a: u32,
+///   b: u16,
+///   c: u16
+/// }
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerSet)]
+/// #[debug_derive]
+/// struct S {
+///   a: u32,
+/// }
+/// ```
+pub use asn1_rs_derive::BerSet;
+
+/// # DerSet custom derive
+///
+/// `DerSet` is a custom derive attribute, to derive both BER and DER [`Set`](super::Set) parsers automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///   - [`CheckDerConstraints`](super::CheckDerConstraints)
+///   - [`FromDer`](super::FromDer)
+///
+/// `DerSet` implies `BerSet`, and will conflict with this custom derive.
+///
+/// Parsers will be automatically derived from struct fields. Every field type must implement the [`FromDer`](super::FromDer) trait.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 structure:
+/// <pre>
+/// S ::= SEt {
+///     a INTEGER(0..2^32),
+///     b INTEGER(0..2^16),
+///     c INTEGER(0..2^16),
+/// }
+/// </pre>
+///
+/// Define a structure and add the `DerSet` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerSet)]
+/// struct S {
+///   a: u32,
+///   b: u16,
+///   c: u16
+/// }
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerSet)]
+/// #[debug_derive]
+/// struct S {
+///   a: u32,
+/// }
+/// ```
+pub use asn1_rs_derive::DerSet;
+
+/// # BerAlias custom derive
+///
+/// `BerAlias` is a custom derive attribute, to derive a BER object parser automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///   - [`CheckDerConstraints`](super::CheckDerConstraints)
+///   - [`FromDer`](super::FromDer)
+///
+/// `DerAlias` implies `BerAlias`, and will conflict with this custom derive. Use `BerAlias` when you only want the
+/// above traits derived.
+///
+/// When defining alias, only unnamed (tuple) structs with one field are supported.
+///
+/// Parser will be automatically derived from the struct field. This field type must implement the `TryFrom<Any>` trait.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 object:
+/// <pre>
+/// MyInt ::= INTEGER(0..2^32)
+/// </pre>
+///
+/// Define a structure and add the `BerAlias` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerAlias)]
+/// struct S(pub u32);
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(BerAlias)]
+/// #[debug_derive]
+/// struct S(pub u32);
+/// ```
+pub use asn1_rs_derive::BerAlias;
+
+/// # DerAlias custom derive
+///
+/// `DerAlias` is a custom derive attribute, to derive a DER object parser automatically from the structure definition.
+/// This attribute will automatically derive implementations for the following traits:
+///   - [`TryFrom<Any>`](super::Any), also providing [`FromBer`](super::FromBer)
+///   - [`Tagged`](super::Tagged)
+///
+/// `DerAlias` implies `BerAlias`, and will conflict with this custom derive.
+///
+/// When defining alias, only unnamed (tuple) structs with one field are supported.
+///
+/// Parser will be automatically derived from the struct field. This field type must implement the `TryFrom<Any>` and `FromDer` traits.
+///
+/// See [`derive`](crate::doc::derive) documentation for more examples and documentation.
+///
+/// ## Examples
+///
+/// To parse the following ASN.1 object:
+/// <pre>
+/// MyInt ::= INTEGER(0..2^32)
+/// </pre>
+///
+/// Define a structure and add the `DerAlias` derive:
+///
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerAlias)]
+/// struct S(pub u32);
+/// ```
+///
+/// ## Debugging
+///
+/// To help debugging the generated code, the `#[debug_derive]` attribute has been added.
+///
+/// When this attribute is specified, the generated code will be printed to `stderr` during compilation.
+///
+/// Example:
+/// ```rust
+/// use asn1_rs::*;
+///
+/// #[derive(DerAlias)]
+/// #[debug_derive]
+/// struct S(pub u32);
+/// ```
+pub use asn1_rs_derive::DerAlias;
diff --git a/src/doc/mod.rs b/src/doc/mod.rs
new file mode 100644
index 0000000..331b350
--- /dev/null
+++ b/src/doc/mod.rs
@@ -0,0 +1,7 @@
+//! Additional documentation: recipes, specific use cases and examples, etc.
+
+#[doc = include_str!("../../doc/RECIPES.md")]
+pub mod recipes {}
+
+#[doc = include_str!("../../doc/DERIVE.md")]
+pub mod derive {}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..e1414c3
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,201 @@
+use crate::{Class, Tag};
+use alloc::str;
+use alloc::string;
+use alloc::string::String;
+use displaydoc::Display;
+use nom::error::{ErrorKind, FromExternalError, ParseError};
+use nom::IResult;
+#[cfg(feature = "std")]
+use std::io;
+#[cfg(feature = "std")]
+use thiserror::Error;
+
+#[cfg(feature = "std")]
+impl std::error::Error for DerConstraint {}
+
+#[derive(Clone, Copy, Debug, Display, PartialEq, Eq)]
+/// Error types for DER constraints
+pub enum DerConstraint {
+    /// Indefinite length not allowed
+    IndefiniteLength,
+    /// Object must not be constructed
+    Constructed,
+    /// Object must be constructed
+    NotConstructed,
+    /// DateTime object is missing timezone
+    MissingTimeZone,
+    /// DateTime object is missing seconds
+    MissingSeconds,
+    /// Bitstring unused bits must be set to zero
+    UnusedBitsNotZero,
+    /// Boolean value must be 0x00 of 0xff
+    InvalidBoolean,
+    /// Integer must not be empty
+    IntegerEmpty,
+    /// Leading zeroes in Integer encoding
+    IntegerLeadingZeroes,
+    /// Leading 0xff in negative Integer encoding
+    IntegerLeadingFF,
+}
+
+// XXX
+// thiserror does not work in no_std
+// see https://github.com/dtolnay/thiserror/pull/64
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+/// The error type for operations of the [`FromBer`](crate::FromBer),
+/// [`FromDer`](crate::FromDer), and associated traits.
+#[derive(Clone, Debug, Display, PartialEq, Eq)]
+// #[cfg_attr(feature = "std", derive(Error))]
+pub enum Error {
+    /// BER object does not have the expected type
+    BerTypeError,
+    /// BER object does not have the expected value
+    BerValueError,
+    /// Invalid Length
+    InvalidLength,
+    /// Invalid Value when parsing object with tag {tag:?} {msg:}
+    InvalidValue { tag: Tag, msg: String },
+    /// Invalid Tag
+    InvalidTag,
+    /// Unknown tag: {0:?}
+    UnknownTag(u32),
+    /// Unexpected Tag (expected: {expected:?}, actual: {actual:?})
+    UnexpectedTag { expected: Option<Tag>, actual: Tag },
+    /// Unexpected Class (expected: {expected:?}, actual: {actual:?})
+    UnexpectedClass {
+        expected: Option<Class>,
+        actual: Class,
+    },
+
+    /// Indefinite length not allowed
+    IndefiniteLengthUnexpected,
+
+    /// DER object was expected to be constructed (and found to be primitive)
+    ConstructExpected,
+    /// DER object was expected to be primitive (and found to be constructed)
+    ConstructUnexpected,
+
+    /// Integer too large to fit requested type
+    IntegerTooLarge,
+    /// BER integer is negative, while an unsigned integer was requested
+    IntegerNegative,
+    /// BER recursive parsing reached maximum depth
+    BerMaxDepth,
+
+    /// Invalid encoding or forbidden characters in string
+    StringInvalidCharset,
+    /// Invalid Date or Time
+    InvalidDateTime,
+
+    /// DER Failed constraint
+    DerConstraintFailed(DerConstraint),
+
+    /// Requesting borrowed data from a temporary object
+    LifetimeError,
+    /// Feature is not yet implemented
+    Unsupported,
+
+    /// incomplete data, missing: {0:?}
+    Incomplete(nom::Needed),
+
+    /// nom error: {0:?}
+    NomError(ErrorKind),
+}
+
+impl Error {
+    /// Build an error from the provided invalid value
+    #[inline]
+    pub const fn invalid_value(tag: Tag, msg: String) -> Self {
+        Self::InvalidValue { tag, msg }
+    }
+
+    /// Build an error from the provided unexpected class
+    #[inline]
+    pub const fn unexpected_class(expected: Option<Class>, actual: Class) -> Self {
+        Self::UnexpectedClass { expected, actual }
+    }
+
+    /// Build an error from the provided unexpected tag
+    #[inline]
+    pub const fn unexpected_tag(expected: Option<Tag>, actual: Tag) -> Self {
+        Self::UnexpectedTag { expected, actual }
+    }
+}
+
+impl<'a> ParseError<&'a [u8]> for Error {
+    fn from_error_kind(_input: &'a [u8], kind: ErrorKind) -> Self {
+        Error::NomError(kind)
+    }
+    fn append(_input: &'a [u8], kind: ErrorKind, _other: Self) -> Self {
+        Error::NomError(kind)
+    }
+}
+
+impl From<Error> for nom::Err<Error> {
+    fn from(e: Error) -> Self {
+        nom::Err::Error(e)
+    }
+}
+
+impl From<str::Utf8Error> for Error {
+    fn from(_: str::Utf8Error) -> Self {
+        Error::StringInvalidCharset
+    }
+}
+
+impl From<string::FromUtf8Error> for Error {
+    fn from(_: string::FromUtf8Error) -> Self {
+        Error::StringInvalidCharset
+    }
+}
+
+impl From<string::FromUtf16Error> for Error {
+    fn from(_: string::FromUtf16Error) -> Self {
+        Error::StringInvalidCharset
+    }
+}
+
+impl From<nom::Err<Error>> for Error {
+    fn from(e: nom::Err<Error>) -> Self {
+        match e {
+            nom::Err::Incomplete(n) => Self::Incomplete(n),
+            nom::Err::Error(e) | nom::Err::Failure(e) => e,
+        }
+    }
+}
+
+impl<I, E> FromExternalError<I, E> for Error {
+    fn from_external_error(_input: I, kind: ErrorKind, _e: E) -> Error {
+        Error::NomError(kind)
+    }
+}
+
+/// Holds the result of BER/DER serialization functions
+pub type ParseResult<'a, T, E = Error> = IResult<&'a [u8], T, E>;
+
+/// A specialized `Result` type for all operations from this crate.
+pub type Result<T, E = Error> = core::result::Result<T, E>;
+
+/// The error type for serialization operations of the [`ToDer`](crate::ToDer) trait.
+#[cfg(feature = "std")]
+#[derive(Debug, Error)]
+pub enum SerializeError {
+    #[error("ASN.1 error: {0:?}")]
+    ASN1Error(#[from] Error),
+
+    #[error("Invalid Class {class:}")]
+    InvalidClass { class: u8 },
+
+    #[error("Invalid Length")]
+    InvalidLength,
+
+    #[error("I/O error: {0:?}")]
+    IOError(#[from] io::Error),
+}
+
+#[cfg(feature = "std")]
+/// Holds the result of BER/DER encoding functions
+pub type SerializeResult<T> = std::result::Result<T, SerializeError>;
diff --git a/src/header.rs b/src/header.rs
new file mode 100644
index 0000000..c2b9aa5
--- /dev/null
+++ b/src/header.rs
@@ -0,0 +1,490 @@
+use crate::ber::*;
+use crate::der_constraint_fail_if;
+use crate::error::*;
+#[cfg(feature = "std")]
+use crate::ToDer;
+use crate::{BerParser, Class, DerParser, DynTagged, FromBer, FromDer, Length, Tag, ToStatic};
+use alloc::borrow::Cow;
+use core::convert::TryFrom;
+use nom::bytes::streaming::take;
+
+/// BER/DER object header (identifier and length)
+#[derive(Clone, Debug)]
+pub struct Header<'a> {
+    /// Object class: universal, application, context-specific, or private
+    pub(crate) class: Class,
+    /// Constructed attribute: true if constructed, else false
+    pub(crate) constructed: bool,
+    /// Tag number
+    pub(crate) tag: Tag,
+    /// Object length: value if definite, or indefinite
+    pub(crate) length: Length,
+
+    /// Optionally, the raw encoding of the tag
+    ///
+    /// This is useful in some cases, where different representations of the same
+    /// BER tags have different meanings (BER only)
+    pub(crate) raw_tag: Option<Cow<'a, [u8]>>,
+}
+
+impl<'a> Header<'a> {
+    /// Build a new BER/DER header from the provided values
+    pub const fn new(class: Class, constructed: bool, tag: Tag, length: Length) -> Self {
+        Header {
+            tag,
+            constructed,
+            class,
+            length,
+            raw_tag: None,
+        }
+    }
+
+    /// Build a new BER/DER header from the provided tag, with default values for other fields
+    #[inline]
+    pub const fn new_simple(tag: Tag) -> Self {
+        let constructed = matches!(tag, Tag::Sequence | Tag::Set);
+        Self::new(Class::Universal, constructed, tag, Length::Definite(0))
+    }
+
+    /// Set the class of this `Header`
+    #[inline]
+    pub fn with_class(self, class: Class) -> Self {
+        Self { class, ..self }
+    }
+
+    /// Set the constructed flags of this `Header`
+    #[inline]
+    pub fn with_constructed(self, constructed: bool) -> Self {
+        Self {
+            constructed,
+            ..self
+        }
+    }
+
+    /// Set the tag of this `Header`
+    #[inline]
+    pub fn with_tag(self, tag: Tag) -> Self {
+        Self { tag, ..self }
+    }
+
+    /// Set the length of this `Header`
+    #[inline]
+    pub fn with_length(self, length: Length) -> Self {
+        Self { length, ..self }
+    }
+
+    /// Update header to add reference to raw tag
+    #[inline]
+    pub fn with_raw_tag(self, raw_tag: Option<Cow<'a, [u8]>>) -> Self {
+        Header { raw_tag, ..self }
+    }
+
+    /// Return the class of this header.
+    #[inline]
+    pub const fn class(&self) -> Class {
+        self.class
+    }
+
+    /// Return true if this header has the 'constructed' flag.
+    #[inline]
+    pub const fn constructed(&self) -> bool {
+        self.constructed
+    }
+
+    /// Return the tag of this header.
+    #[inline]
+    pub const fn tag(&self) -> Tag {
+        self.tag
+    }
+
+    /// Return the length of this header.
+    #[inline]
+    pub const fn length(&self) -> Length {
+        self.length
+    }
+
+    /// Return the raw tag encoding, if it was stored in this object
+    #[inline]
+    pub fn raw_tag(&self) -> Option<&[u8]> {
+        self.raw_tag.as_ref().map(|cow| cow.as_ref())
+    }
+
+    /// Test if object is primitive
+    #[inline]
+    pub const fn is_primitive(&self) -> bool {
+        !self.constructed
+    }
+
+    /// Test if object is constructed
+    #[inline]
+    pub const fn is_constructed(&self) -> bool {
+        self.constructed
+    }
+
+    /// Return error if class is not the expected class
+    #[inline]
+    pub const fn assert_class(&self, class: Class) -> Result<()> {
+        self.class.assert_eq(class)
+    }
+
+    /// Return error if tag is not the expected tag
+    #[inline]
+    pub const fn assert_tag(&self, tag: Tag) -> Result<()> {
+        self.tag.assert_eq(tag)
+    }
+
+    /// Return error if object is not primitive
+    #[inline]
+    pub const fn assert_primitive(&self) -> Result<()> {
+        if self.is_primitive() {
+            Ok(())
+        } else {
+            Err(Error::ConstructUnexpected)
+        }
+    }
+
+    /// Return error if object is primitive
+    #[inline]
+    pub const fn assert_constructed(&self) -> Result<()> {
+        if !self.is_primitive() {
+            Ok(())
+        } else {
+            Err(Error::ConstructExpected)
+        }
+    }
+
+    /// Test if object class is Universal
+    #[inline]
+    pub const fn is_universal(&self) -> bool {
+        self.class as u8 == Class::Universal as u8
+    }
+    /// Test if object class is Application
+    #[inline]
+    pub const fn is_application(&self) -> bool {
+        self.class as u8 == Class::Application as u8
+    }
+    /// Test if object class is Context-specific
+    #[inline]
+    pub const fn is_contextspecific(&self) -> bool {
+        self.class as u8 == Class::ContextSpecific as u8
+    }
+    /// Test if object class is Private
+    #[inline]
+    pub const fn is_private(&self) -> bool {
+        self.class as u8 == Class::Private as u8
+    }
+
+    /// Return error if object length is definite
+    #[inline]
+    pub const fn assert_definite(&self) -> Result<()> {
+        if self.length.is_definite() {
+            Ok(())
+        } else {
+            Err(Error::DerConstraintFailed(DerConstraint::IndefiniteLength))
+        }
+    }
+
+    /// Get the content following a BER header
+    #[inline]
+    pub fn parse_ber_content<'i>(&'_ self, i: &'i [u8]) -> ParseResult<'i, &'i [u8]> {
+        // defaults to maximum depth 8
+        // depth is used only if BER, and length is indefinite
+        BerParser::get_object_content(i, self, 8)
+    }
+
+    /// Get the content following a DER header
+    #[inline]
+    pub fn parse_der_content<'i>(&'_ self, i: &'i [u8]) -> ParseResult<'i, &'i [u8]> {
+        self.assert_definite()?;
+        DerParser::get_object_content(i, self, 8)
+    }
+}
+
+impl From<Tag> for Header<'_> {
+    #[inline]
+    fn from(tag: Tag) -> Self {
+        let constructed = matches!(tag, Tag::Sequence | Tag::Set);
+        Self::new(Class::Universal, constructed, tag, Length::Definite(0))
+    }
+}
+
+impl<'a> ToStatic for Header<'a> {
+    type Owned = Header<'static>;
+
+    fn to_static(&self) -> Self::Owned {
+        let raw_tag: Option<Cow<'static, [u8]>> =
+            self.raw_tag.as_ref().map(|b| Cow::Owned(b.to_vec()));
+        Header {
+            tag: self.tag,
+            constructed: self.constructed,
+            class: self.class,
+            length: self.length,
+            raw_tag,
+        }
+    }
+}
+
+impl<'a> FromBer<'a> for Header<'a> {
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+        let (i1, el) = parse_identifier(bytes)?;
+        let class = match Class::try_from(el.0) {
+            Ok(c) => c,
+            Err(_) => unreachable!(), // Cannot fail, we have read exactly 2 bits
+        };
+        let (i2, len) = parse_ber_length_byte(i1)?;
+        let (i3, len) = match (len.0, len.1) {
+            (0, l1) => {
+                // Short form: MSB is 0, the rest encodes the length (which can be 0) (8.1.3.4)
+                (i2, Length::Definite(usize::from(l1)))
+            }
+            (_, 0) => {
+                // Indefinite form: MSB is 1, the rest is 0 (8.1.3.6)
+                // If encoding is primitive, definite form shall be used (8.1.3.2)
+                if el.1 == 0 {
+                    return Err(nom::Err::Error(Error::ConstructExpected));
+                }
+                (i2, Length::Indefinite)
+            }
+            (_, l1) => {
+                // if len is 0xff -> error (8.1.3.5)
+                if l1 == 0b0111_1111 {
+                    return Err(::nom::Err::Error(Error::InvalidLength));
+                }
+                let (i3, llen) = take(l1)(i2)?;
+                match bytes_to_u64(llen) {
+                    Ok(l) => {
+                        let l =
+                            usize::try_from(l).or(Err(::nom::Err::Error(Error::InvalidLength)))?;
+                        (i3, Length::Definite(l))
+                    }
+                    Err(_) => {
+                        return Err(::nom::Err::Error(Error::InvalidLength));
+                    }
+                }
+            }
+        };
+        let constructed = el.1 != 0;
+        let hdr = Header::new(class, constructed, Tag(el.2), len).with_raw_tag(Some(el.3.into()));
+        Ok((i3, hdr))
+    }
+}
+
+impl<'a> FromDer<'a> for Header<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
+        let (i1, el) = parse_identifier(bytes)?;
+        let class = match Class::try_from(el.0) {
+            Ok(c) => c,
+            Err(_) => unreachable!(), // Cannot fail, we have read exactly 2 bits
+        };
+        let (i2, len) = parse_ber_length_byte(i1)?;
+        let (i3, len) = match (len.0, len.1) {
+            (0, l1) => {
+                // Short form: MSB is 0, the rest encodes the length (which can be 0) (8.1.3.4)
+                (i2, Length::Definite(usize::from(l1)))
+            }
+            (_, 0) => {
+                // Indefinite form is not allowed in DER (10.1)
+                return Err(::nom::Err::Error(Error::DerConstraintFailed(
+                    DerConstraint::IndefiniteLength,
+                )));
+            }
+            (_, l1) => {
+                // if len is 0xff -> error (8.1.3.5)
+                if l1 == 0b0111_1111 {
+                    return Err(::nom::Err::Error(Error::InvalidLength));
+                }
+                // DER(9.1) if len is 0 (indefinite form), obj must be constructed
+                der_constraint_fail_if!(
+                    &i[1..],
+                    len.1 == 0 && el.1 != 1,
+                    DerConstraint::NotConstructed
+                );
+                let (i3, llen) = take(l1)(i2)?;
+                match bytes_to_u64(llen) {
+                    Ok(l) => {
+                        // DER: should have been encoded in short form (< 127)
+                        // XXX der_constraint_fail_if!(i, l < 127);
+                        let l =
+                            usize::try_from(l).or(Err(::nom::Err::Error(Error::InvalidLength)))?;
+                        (i3, Length::Definite(l))
+                    }
+                    Err(_) => {
+                        return Err(::nom::Err::Error(Error::InvalidLength));
+                    }
+                }
+            }
+        };
+        let constructed = el.1 != 0;
+        let hdr = Header::new(class, constructed, Tag(el.2), len).with_raw_tag(Some(el.3.into()));
+        Ok((i3, hdr))
+    }
+}
+
+impl DynTagged for (Class, bool, Tag) {
+    fn tag(&self) -> Tag {
+        self.2
+    }
+}
+
+#[cfg(feature = "std")]
+impl ToDer for (Class, bool, Tag) {
+    fn to_der_len(&self) -> Result<usize> {
+        let (_, _, tag) = self;
+        match tag.0 {
+            0..=30 => Ok(1),
+            t => {
+                let mut sz = 1;
+                let mut val = t;
+                loop {
+                    if val <= 127 {
+                        return Ok(sz + 1);
+                    } else {
+                        val >>= 7;
+                        sz += 1;
+                    }
+                }
+            }
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let (class, constructed, tag) = self;
+        let b0 = (*class as u8) << 6;
+        let b0 = b0 | if *constructed { 0b10_0000 } else { 0 };
+        if tag.0 > 30 {
+            let b0 = b0 | 0b1_1111;
+            let mut sz = writer.write(&[b0])?;
+            let mut val = tag.0;
+            loop {
+                if val <= 127 {
+                    sz += writer.write(&[val as u8])?;
+                    return Ok(sz);
+                } else {
+                    let b = (val & 0b0111_1111) as u8 | 0b1000_0000;
+                    sz += writer.write(&[b])?;
+                    val >>= 7;
+                }
+            }
+        } else {
+            let b0 = b0 | (tag.0 as u8);
+            let sz = writer.write(&[b0])?;
+            Ok(sz)
+        }
+    }
+
+    fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        Ok(0)
+    }
+}
+
+impl DynTagged for Header<'_> {
+    fn tag(&self) -> Tag {
+        self.tag
+    }
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Header<'_> {
+    fn to_der_len(&self) -> Result<usize> {
+        let tag_len = (self.class, self.constructed, self.tag).to_der_len()?;
+        let len_len = self.length.to_der_len()?;
+        Ok(tag_len + len_len)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let sz = (self.class, self.constructed, self.tag).write_der_header(writer)?;
+        let sz = sz + self.length.write_der_header(writer)?;
+        Ok(sz)
+    }
+
+    fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        Ok(0)
+    }
+
+    fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // use raw_tag if present
+        let sz = match &self.raw_tag {
+            Some(t) => writer.write(t)?,
+            None => (self.class, self.constructed, self.tag).write_der_header(writer)?,
+        };
+        let sz = sz + self.length.write_der_header(writer)?;
+        Ok(sz)
+    }
+}
+
+/// Compare two BER headers. `len` fields are compared only if both objects have it set (same for `raw_tag`)
+impl<'a> PartialEq<Header<'a>> for Header<'a> {
+    fn eq(&self, other: &Header) -> bool {
+        self.class == other.class
+            && self.tag == other.tag
+            && self.constructed == other.constructed
+            && {
+                if self.length.is_null() && other.length.is_null() {
+                    self.length == other.length
+                } else {
+                    true
+                }
+            }
+            && {
+                // it tag is present for both, compare it
+                if self.raw_tag.as_ref().xor(other.raw_tag.as_ref()).is_none() {
+                    self.raw_tag == other.raw_tag
+                } else {
+                    true
+                }
+            }
+    }
+}
+
+impl Eq for Header<'_> {}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use hex_literal::hex;
+
+    /// Generic tests on methods, and coverage tests
+    #[test]
+    fn methods_header() {
+        // Getters
+        let input = &hex! {"02 01 00"};
+        let (rem, header) = Header::from_ber(input).expect("parsing header failed");
+        assert_eq!(header.class(), Class::Universal);
+        assert_eq!(header.tag(), Tag::Integer);
+        assert!(header.assert_primitive().is_ok());
+        assert!(header.assert_constructed().is_err());
+        assert!(header.is_universal());
+        assert!(!header.is_application());
+        assert!(!header.is_private());
+        assert_eq!(rem, &input[2..]);
+
+        // test PartialEq
+        let hdr2 = Header::new_simple(Tag::Integer);
+        assert_eq!(header, hdr2);
+
+        // builder methods
+        let hdr3 = hdr2
+            .with_class(Class::ContextSpecific)
+            .with_constructed(true)
+            .with_length(Length::Definite(1));
+        assert!(hdr3.constructed());
+        assert!(hdr3.is_constructed());
+        assert!(hdr3.assert_constructed().is_ok());
+        assert!(hdr3.is_contextspecific());
+        let xx = hdr3.to_der_vec().expect("serialize failed");
+        assert_eq!(&xx, &[0xa2, 0x01]);
+
+        // indefinite length
+        let hdr4 = hdr3.with_length(Length::Indefinite);
+        assert!(hdr4.assert_definite().is_err());
+        let xx = hdr4.to_der_vec().expect("serialize failed");
+        assert_eq!(&xx, &[0xa2, 0x80]);
+
+        // parse_*_content
+        let hdr = Header::new_simple(Tag(2)).with_length(Length::Definite(1));
+        let (_, r) = hdr.parse_ber_content(&input[2..]).unwrap();
+        assert_eq!(r, &input[2..]);
+        let (_, r) = hdr.parse_der_content(&input[2..]).unwrap();
+        assert_eq!(r, &input[2..]);
+    }
+}
diff --git a/src/length.rs b/src/length.rs
new file mode 100644
index 0000000..16688ae
--- /dev/null
+++ b/src/length.rs
@@ -0,0 +1,180 @@
+use crate::{DynTagged, Error, Result, Tag};
+#[cfg(feature = "std")]
+use crate::{SerializeResult, ToDer};
+use core::ops;
+
+/// BER Object Length
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Length {
+    /// Definite form (X.690 8.1.3.3)
+    Definite(usize),
+    /// Indefinite form (X.690 8.1.3.6)
+    Indefinite,
+}
+
+impl Length {
+    /// Return true if length is definite and equal to 0
+    #[inline]
+    pub fn is_null(&self) -> bool {
+        *self == Length::Definite(0)
+    }
+
+    /// Get length of primitive object
+    #[inline]
+    pub fn definite(&self) -> Result<usize> {
+        match self {
+            Length::Definite(sz) => Ok(*sz),
+            Length::Indefinite => Err(Error::IndefiniteLengthUnexpected),
+        }
+    }
+
+    /// Return true if length is definite
+    #[inline]
+    pub const fn is_definite(&self) -> bool {
+        matches!(self, Length::Definite(_))
+    }
+
+    /// Return error if length is not definite
+    #[inline]
+    pub const fn assert_definite(&self) -> Result<()> {
+        match self {
+            Length::Definite(_) => Ok(()),
+            Length::Indefinite => Err(Error::IndefiniteLengthUnexpected),
+        }
+    }
+}
+
+impl From<usize> for Length {
+    fn from(l: usize) -> Self {
+        Length::Definite(l)
+    }
+}
+
+impl ops::Add<Length> for Length {
+    type Output = Self;
+
+    fn add(self, rhs: Length) -> Self::Output {
+        match self {
+            Length::Indefinite => self,
+            Length::Definite(lhs) => match rhs {
+                Length::Indefinite => rhs,
+                Length::Definite(rhs) => Length::Definite(lhs + rhs),
+            },
+        }
+    }
+}
+
+impl ops::Add<usize> for Length {
+    type Output = Self;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        match self {
+            Length::Definite(lhs) => Length::Definite(lhs + rhs),
+            Length::Indefinite => self,
+        }
+    }
+}
+
+impl ops::AddAssign<usize> for Length {
+    fn add_assign(&mut self, rhs: usize) {
+        match self {
+            Length::Definite(ref mut lhs) => *lhs += rhs,
+            Length::Indefinite => (),
+        }
+    }
+}
+
+impl DynTagged for Length {
+    fn tag(&self) -> Tag {
+        Tag(0)
+    }
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Length {
+    fn to_der_len(&self) -> Result<usize> {
+        match self {
+            Length::Indefinite => Ok(1),
+            Length::Definite(l) => match l {
+                0..=0x7f => Ok(1),
+                0x80..=0xff => Ok(2),
+                0x100..=0xffff => Ok(3),
+                0x1_0000..=0xffff_ffff => Ok(4),
+                _ => Err(Error::InvalidLength),
+            },
+        }
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        match *self {
+            Length::Indefinite => {
+                let sz = writer.write(&[0b1000_0000])?;
+                Ok(sz)
+            }
+            Length::Definite(l) => {
+                if l <= 127 {
+                    // Short form
+                    let sz = writer.write(&[l as u8])?;
+                    Ok(sz)
+                } else {
+                    // Long form
+                    let b = l.to_be_bytes();
+                    // skip leading zeroes
+                    // we do not have to test for length, l cannot be 0
+                    let mut idx = 0;
+                    while b[idx] == 0 {
+                        idx += 1;
+                    }
+                    let b = &b[idx..];
+                    // first byte: 0x80 + length of length
+                    let b0 = 0x80 | (b.len() as u8);
+                    let sz = writer.write(&[b0])?;
+                    let sz = sz + writer.write(b)?;
+                    Ok(sz)
+                }
+            }
+        }
+    }
+
+    fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        Ok(0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+
+    /// Generic and coverage tests
+    #[test]
+    fn methods_length() {
+        let l = Length::from(2);
+        assert_eq!(l.definite(), Ok(2));
+        assert!(l.assert_definite().is_ok());
+
+        let l = Length::Indefinite;
+        assert!(l.definite().is_err());
+        assert!(l.assert_definite().is_err());
+
+        let l = Length::from(2);
+        assert_eq!(l + 2, Length::from(4));
+        assert_eq!(l + Length::Indefinite, Length::Indefinite);
+
+        let l = Length::Indefinite;
+        assert_eq!(l + 2, Length::Indefinite);
+
+        let l = Length::from(2);
+        assert_eq!(l + Length::from(2), Length::from(4));
+
+        let l = Length::Indefinite;
+        assert_eq!(l + Length::from(2), Length::Indefinite);
+
+        let mut l = Length::from(2);
+        l += 2;
+        assert_eq!(l.definite(), Ok(4));
+
+        let mut l = Length::Indefinite;
+        l += 2;
+        assert_eq!(l, Length::Indefinite);
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..c1275f3
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,214 @@
+//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT)
+//! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE)
+//! [![docs.rs](https://docs.rs/asn1-rs/badge.svg)](https://docs.rs/asn1-rs)
+//! [![crates.io](https://img.shields.io/crates/v/asn1-rs.svg)](https://crates.io/crates/asn1-rs)
+//! [![Download numbers](https://img.shields.io/crates/d/asn1-rs.svg)](https://crates.io/crates/asn1-rs)
+//! [![Github CI](https://github.com/rusticata/asn1-rs/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/asn1-rs/actions)
+//! [![Minimum rustc version](https://img.shields.io/badge/rustc-1.53.0+-lightgray.svg)](#rust-version-requirements)
+//!
+//! # BER/DER Parsers/Encoders
+//!
+//! A set of parsers/encoders for Basic Encoding Rules (BER [[X.690]]) and Distinguished Encoding Rules(DER
+//! [[X.690]]) formats, implemented with the [nom] parser combinator framework.
+//!
+//! It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken
+//! to ensure security and safety of this crate, including design (recursion limit, defensive
+//! programming), tests, and fuzzing. It also aims to be panic-free.
+//!
+//! This crate is a rewrite of [der-parser](https://crates.io/crates/der-parser) to propose a more data-oriented API,
+//! and add generalized support for serialization.
+//!
+//! Many ideas were borrowed from the [crypto/utils/der](https://github.com/RustCrypto/utils/tree/master/der) crate (like
+//! the `Any`/`TryFrom`/`FromDer` mechanism), adapted and merged into a generalized BER/DER crate.
+//! Credits (and many thanks) go to Tony Arcieri for writing the original crate.
+//!
+//! # BER/DER parsers
+//!
+//! BER stands for Basic Encoding Rules, and is defined in [[X.690]]. It defines a set of rules to
+//! encode and decode ASN.1 [[X.680]] objects in binary.
+//!
+//! [[X.690]] also defines Distinguished Encoding Rules (DER), which is BER with added rules to
+//! ensure canonical and unequivocal binary representation of objects.
+//!
+//! The choice of which one to use is usually guided by the speficication of the data format based
+//! on BER or DER: for example, X.509 uses DER as encoding representation.
+//!
+//! The main traits for parsing are the [`FromBer`] and [`FromDer`] traits.
+//! These traits provide methods to parse binary input, and return either the remaining (unparsed) bytes
+//! and the parsed object, or an error.
+//!
+//! The parsers follow the interface from [nom], and the [`ParseResult`] object is a specialized version
+//! of `nom::IResult`. This means that most `nom` combinators (`map`, `many0`, etc.) can be used in
+//! combination to objects and methods from this crate. Reading the nom documentation may
+//! help understanding how to write and combine parsers and use the output.
+//!
+//! **Minimum Supported Rust Version**: 1.53.0
+//!
+//! Note: if the `bits` feature is enabled, MSRV is 1.56.0 (due to `bitvec` 1.0)
+//!
+//! # Recipes
+//!
+//! See [doc::recipes] and [doc::derive] for more examples and recipes.
+//!
+//! ## Examples
+//!
+//! Parse 2 BER integers:
+//!
+//! ```rust
+//! use asn1_rs::{Integer, FromBer};
+//!
+//! let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
+//!               0x02, 0x03, 0x01, 0x00, 0x00,
+//! ];
+//!
+//! let (rem, obj1) = Integer::from_ber(&bytes).expect("parsing failed");
+//! let (rem, obj2) = Integer::from_ber(&bytes).expect("parsing failed");
+//!
+//! assert_eq!(obj1, Integer::from_u32(65537));
+//! ```
+//!
+//! In the above example, the generic [`Integer`] type is used. This type can contain integers of any
+//! size, but do not provide a simple API to manipulate the numbers.
+//!
+//! In most cases, the integer either has a limit, or is expected to fit into a primitive type.
+//! To get a simple value, just use the `from_ber`/`from_der` methods on the primitive types:
+//!
+//! ```rust
+//! use asn1_rs::FromBer;
+//!
+//! let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
+//!               0x02, 0x03, 0x01, 0x00, 0x00,
+//! ];
+//!
+//! let (rem, obj1) = u32::from_ber(&bytes).expect("parsing failed");
+//! let (rem, obj2) = u32::from_ber(&rem).expect("parsing failed");
+//!
+//! assert_eq!(obj1, 65537);
+//! assert_eq!(obj2, 65536);
+//! ```
+//!
+//! If the parsing succeeds, but the integer cannot fit into the expected type, the method will return
+//! an `IntegerTooLarge` error.
+//!
+//! # BER/DER encoders
+//!
+//! BER/DER encoding is symmetrical to decoding, using the traits `ToBer` and [`ToDer`] traits.
+//! These traits provide methods to write encoded content to objects with the `io::Write` trait,
+//! or return an allocated `Vec<u8>` with the encoded data.
+//! If the serialization fails, an error is returned.
+//!
+//! ## Examples
+//!
+//! Writing 2 BER integers:
+//!
+//! ```rust
+//! use asn1_rs::{Integer, ToDer};
+//!
+//! let mut writer = Vec::new();
+//!
+//! let obj1 = Integer::from_u32(65537);
+//! let obj2 = Integer::from_u32(65536);
+//!
+//! let _ = obj1.write_der(&mut writer).expect("serialization failed");
+//! let _ = obj2.write_der(&mut writer).expect("serialization failed");
+//!
+//! let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
+//!                0x02, 0x03, 0x01, 0x00, 0x00,
+//! ];
+//! assert_eq!(&writer, bytes);
+//! ```
+//!
+//! Similarly to `FromBer`/`FromDer`, serialization methods are also implemented for primitive types:
+//!
+//! ```rust
+//! use asn1_rs::ToDer;
+//!
+//! let mut writer = Vec::new();
+//!
+//! let _ = 65537.write_der(&mut writer).expect("serialization failed");
+//! let _ = 65536.write_der(&mut writer).expect("serialization failed");
+//!
+//! let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
+//!                0x02, 0x03, 0x01, 0x00, 0x00,
+//! ];
+//! assert_eq!(&writer, bytes);
+//! ```
+//!
+//! If the parsing succeeds, but the integer cannot fit into the expected type, the method will return
+//! an `IntegerTooLarge` error.
+//!
+//! ## Changes
+//!
+//! See `CHANGELOG.md`.
+//!
+//! # References
+//!
+//! - [[X.680]] Abstract Syntax Notation One (ASN.1): Specification of basic notation.
+//! - [[X.690]] ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical
+//!   Encoding Rules (CER) and Distinguished Encoding Rules (DER).
+//!
+//! [X.680]: http://www.itu.int/rec/T-REC-X.680/en "Abstract Syntax Notation One (ASN.1):
+//!   Specification of basic notation."
+//! [X.690]: https://www.itu.int/rec/T-REC-X.690/en "ASN.1 encoding rules: Specification of
+//!   Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules
+//!   (DER)."
+//! [nom]: https://github.com/Geal/nom "Nom parser combinator framework"
+#![deny(/*missing_docs,*/
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications,
+    // unreachable_pub
+)]
+#![forbid(unsafe_code)]
+#![warn(
+/* missing_docs,
+rust_2018_idioms,*/
+missing_debug_implementations,
+)]
+// pragmas for doc
+#![deny(rustdoc::broken_intra_doc_links)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![doc(test(
+no_crate_inject,
+attr(deny(warnings/*, rust_2018_idioms*/), allow(dead_code, unused_variables))
+))]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "std")]
+extern crate core;
+
+// #[cfg(feature = "alloc")]
+extern crate alloc;
+
+mod asn1_types;
+mod ber;
+mod class;
+mod datetime;
+mod derive;
+mod error;
+mod header;
+mod length;
+mod tag;
+mod traits;
+
+pub use asn1_types::*;
+pub use class::*;
+pub use datetime::*;
+pub use derive::*;
+pub use error::*;
+pub use header::*;
+pub use length::*;
+pub use tag::*;
+pub use traits::*;
+
+pub use nom;
+pub use nom::{Err, IResult, Needed};
+
+#[doc(hidden)]
+pub mod exports {
+    pub use alloc::borrow;
+    pub use asn1_rs_impl;
+}
+
+#[cfg(doc)]
+pub mod doc;
diff --git a/src/tag.rs b/src/tag.rs
new file mode 100644
index 0000000..c7130c1
--- /dev/null
+++ b/src/tag.rs
@@ -0,0 +1,74 @@
+use crate::{Error, Result};
+use alloc::string::ToString;
+use rusticata_macros::newtype_enum;
+
+/// BER/DER Tag as defined in X.680 section 8.4
+///
+/// X.690 doesn't specify the maximum tag size so we're assuming that people
+/// aren't going to need anything more than a u32.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Tag(pub u32);
+
+newtype_enum! {
+impl display Tag {
+    EndOfContent = 0,
+    Boolean = 1,
+    Integer = 2,
+    BitString = 3,
+    OctetString = 4,
+    Null = 5,
+    Oid = 6,
+    ObjectDescriptor = 7,
+    External = 8,
+    RealType = 9,
+    Enumerated = 10,
+    EmbeddedPdv = 11,
+    Utf8String = 12,
+    RelativeOid = 13,
+
+    Sequence = 16,
+    Set = 17,
+    NumericString = 18,
+    PrintableString = 19,
+    T61String = 20,
+    TeletexString = 20,
+    VideotexString = 21,
+
+    Ia5String = 22,
+    UtcTime = 23,
+    GeneralizedTime = 24,
+
+    GraphicString = 25,
+    VisibleString = 26,
+    GeneralString = 27,
+
+    UniversalString = 28,
+    BmpString = 30,
+}
+}
+
+impl Tag {
+    pub const fn assert_eq(&self, tag: Tag) -> Result<()> {
+        if self.0 == tag.0 {
+            Ok(())
+        } else {
+            Err(Error::UnexpectedTag {
+                expected: Some(tag),
+                actual: *self,
+            })
+        }
+    }
+
+    pub fn invalid_value(&self, msg: &str) -> Error {
+        Error::InvalidValue {
+            tag: *self,
+            msg: msg.to_string(),
+        }
+    }
+}
+
+impl From<u32> for Tag {
+    fn from(v: u32) -> Self {
+        Tag(v)
+    }
+}
diff --git a/src/traits.rs b/src/traits.rs
new file mode 100644
index 0000000..eea4cb9
--- /dev/null
+++ b/src/traits.rs
@@ -0,0 +1,348 @@
+use crate::error::*;
+use crate::{Any, Class, Explicit, Implicit, Tag, TaggedParser};
+use core::convert::{TryFrom, TryInto};
+#[cfg(feature = "std")]
+use std::io::Write;
+
+/// Phantom type representing a BER parser
+#[doc(hidden)]
+#[derive(Debug)]
+pub enum BerParser {}
+
+/// Phantom type representing a DER parser
+#[doc(hidden)]
+#[derive(Debug)]
+pub enum DerParser {}
+
+#[doc(hidden)]
+pub trait ASN1Parser {}
+
+impl ASN1Parser for BerParser {}
+impl ASN1Parser for DerParser {}
+
+pub trait Tagged {
+    const TAG: Tag;
+}
+
+impl<T> Tagged for &'_ T
+where
+    T: Tagged,
+{
+    const TAG: Tag = T::TAG;
+}
+
+pub trait DynTagged {
+    fn tag(&self) -> Tag;
+}
+
+impl<T> DynTagged for T
+where
+    T: Tagged,
+{
+    fn tag(&self) -> Tag {
+        T::TAG
+    }
+}
+
+/// Base trait for BER object parsers
+///
+/// Library authors should usually not directly implement this trait, but should prefer implementing the
+/// [`TryFrom<Any>`] trait,
+/// which offers greater flexibility and provides an equivalent `FromBer` implementation for free.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Any, Result, Tag};
+/// use std::convert::TryFrom;
+///
+/// // The type to be decoded
+/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// pub struct MyType(pub u32);
+///
+/// impl<'a> TryFrom<Any<'a>> for MyType {
+///     type Error = asn1_rs::Error;
+///
+///     fn try_from(any: Any<'a>) -> Result<MyType> {
+///         any.tag().assert_eq(Tag::Integer)?;
+///         // for this fictive example, the type contains the number of characters
+///         let n = any.data.len() as u32;
+///         Ok(MyType(n))
+///     }
+/// }
+///
+/// // The above code provides a `FromBer` implementation for free.
+///
+/// // Example of parsing code:
+/// use asn1_rs::FromBer;
+///
+/// let input = &[2, 1, 2];
+/// // Objects can be parsed using `from_ber`, which returns the remaining bytes
+/// // and the parsed object:
+/// let (rem, my_type) = MyType::from_ber(input).expect("parsing failed");
+/// ```
+pub trait FromBer<'a, E = Error>: Sized {
+    /// Attempt to parse input bytes into a BER object
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<Self, E>;
+}
+
+impl<'a, T, E> FromBer<'a, E> for T
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    E: From<Error>,
+{
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<T, E> {
+        let (i, any) = Any::from_ber(bytes).map_err(nom::Err::convert)?;
+        let result = any.try_into().map_err(nom::Err::Error)?;
+        Ok((i, result))
+    }
+}
+
+/// Base trait for DER object parsers
+///
+/// Library authors should usually not directly implement this trait, but should prefer implementing the
+/// [`TryFrom<Any>`] + [`CheckDerConstraints`] traits,
+/// which offers greater flexibility and provides an equivalent `FromDer` implementation for free
+/// (in fact, it provides both [`FromBer`] and `FromDer`).
+///
+/// Note: if you already implemented [`TryFrom<Any>`] and [`CheckDerConstraints`],
+/// you can get a free [`FromDer`] implementation by implementing the
+/// [`DerAutoDerive`] trait. This is not automatic, so it is also possible to manually
+/// implement [`FromDer`] if preferred.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Any, CheckDerConstraints, DerAutoDerive, Result, Tag};
+/// use std::convert::TryFrom;
+///
+/// // The type to be decoded
+/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// pub struct MyType(pub u32);
+///
+/// impl<'a> TryFrom<Any<'a>> for MyType {
+///     type Error = asn1_rs::Error;
+///
+///     fn try_from(any: Any<'a>) -> Result<MyType> {
+///         any.tag().assert_eq(Tag::Integer)?;
+///         // for this fictive example, the type contains the number of characters
+///         let n = any.data.len() as u32;
+///         Ok(MyType(n))
+///     }
+/// }
+///
+/// impl CheckDerConstraints for MyType {
+///     fn check_constraints(any: &Any) -> Result<()> {
+///         any.header.assert_primitive()?;
+///         Ok(())
+///     }
+/// }
+///
+/// impl DerAutoDerive for MyType {}
+///
+/// // The above code provides a `FromDer` implementation for free.
+///
+/// // Example of parsing code:
+/// use asn1_rs::FromDer;
+///
+/// let input = &[2, 1, 2];
+/// // Objects can be parsed using `from_der`, which returns the remaining bytes
+/// // and the parsed object:
+/// let (rem, my_type) = MyType::from_der(input).expect("parsing failed");
+/// ```
+pub trait FromDer<'a, E = Error>: Sized {
+    /// Attempt to parse input bytes into a DER object (enforcing constraints)
+    fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E>;
+}
+
+/// Trait to automatically derive `FromDer`
+///
+/// This trait is only a marker to control if a DER parser should be automatically derived. It is
+/// empty.
+///
+/// This trait is used in combination with others:
+/// after implementing [`TryFrom<Any>`] and [`CheckDerConstraints`] for a type,
+/// a free [`FromDer`] implementation is provided by implementing the
+/// [`DerAutoDerive`] trait. This is the most common case.
+///
+/// However, this is not automatic so it is also possible to manually
+/// implement [`FromDer`] if preferred.
+/// Manual implementation is generally only needed for generic containers (for ex. `Vec<T>`),
+/// because the default implementation adds a constraint on `T` to implement also `TryFrom<Any>`
+/// and `CheckDerConstraints`. This is problematic when `T` only provides `FromDer`, and can be
+/// solved by providing a manual implementation of [`FromDer`].
+pub trait DerAutoDerive {}
+
+impl<'a, T, E> FromDer<'a, E> for T
+where
+    T: TryFrom<Any<'a>, Error = E>,
+    T: CheckDerConstraints,
+    T: DerAutoDerive,
+    E: From<Error>,
+{
+    fn from_der(bytes: &'a [u8]) -> ParseResult<T, E> {
+        // Note: Any::from_der checks than length is definite
+        let (i, any) = Any::from_der(bytes).map_err(nom::Err::convert)?;
+        <T as CheckDerConstraints>::check_constraints(&any)
+            .map_err(|e| nom::Err::Error(e.into()))?;
+        let result = any.try_into().map_err(nom::Err::Error)?;
+        Ok((i, result))
+    }
+}
+
+/// Verification of DER constraints
+pub trait CheckDerConstraints {
+    fn check_constraints(any: &Any) -> Result<()>;
+}
+
+/// Common trait for all objects that can be encoded using the DER representation
+///
+/// # Examples
+///
+/// Objects from this crate can be encoded as DER:
+///
+/// ```
+/// use asn1_rs::{Integer, ToDer};
+///
+/// let int = Integer::from(4u32);
+/// let mut writer = Vec::new();
+/// let sz = int.write_der(&mut writer).expect("serialization failed");
+///
+/// assert_eq!(&writer, &[0x02, 0x01, 0x04]);
+/// # assert_eq!(sz, 3);
+/// ```
+///
+/// Many of the primitive types can also directly be encoded as DER:
+///
+/// ```
+/// use asn1_rs::ToDer;
+///
+/// let mut writer = Vec::new();
+/// let sz = 4.write_der(&mut writer).expect("serialization failed");
+///
+/// assert_eq!(&writer, &[0x02, 0x01, 0x04]);
+/// # assert_eq!(sz, 3);
+/// ```
+#[cfg(feature = "std")]
+pub trait ToDer
+where
+    Self: DynTagged,
+{
+    /// Get the length of the object (including the header), when encoded
+    ///
+    // Since we are using DER, length cannot be Indefinite, so we can use `usize`.
+    // XXX can this function fail?
+    fn to_der_len(&self) -> Result<usize>;
+
+    /// Write the DER encoded representation to a newly allocated `Vec<u8>`.
+    fn to_der_vec(&self) -> SerializeResult<Vec<u8>> {
+        let mut v = Vec::new();
+        let _ = self.write_der(&mut v)?;
+        Ok(v)
+    }
+
+    /// Similar to using `to_vec`, but uses provided values without changes.
+    /// This can generate an invalid encoding for a DER object.
+    fn to_der_vec_raw(&self) -> SerializeResult<Vec<u8>> {
+        let mut v = Vec::new();
+        let _ = self.write_der_raw(&mut v)?;
+        Ok(v)
+    }
+
+    /// Attempt to write the DER encoded representation (header and content) into this writer.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use asn1_rs::{Integer, ToDer};
+    ///
+    /// let int = Integer::from(4u32);
+    /// let mut writer = Vec::new();
+    /// let sz = int.write_der(&mut writer).expect("serialization failed");
+    ///
+    /// assert_eq!(&writer, &[0x02, 0x01, 0x04]);
+    /// # assert_eq!(sz, 3);
+    /// ```
+    fn write_der(&self, writer: &mut dyn Write) -> SerializeResult<usize> {
+        let sz = self.write_der_header(writer)?;
+        let sz = sz + self.write_der_content(writer)?;
+        Ok(sz)
+    }
+
+    /// Attempt to write the DER header to this writer.
+    fn write_der_header(&self, writer: &mut dyn Write) -> SerializeResult<usize>;
+
+    /// Attempt to write the DER content (all except header) to this writer.
+    fn write_der_content(&self, writer: &mut dyn Write) -> SerializeResult<usize>;
+
+    /// Similar to using `to_der`, but uses provided values without changes.
+    /// This can generate an invalid encoding for a DER object.
+    fn write_der_raw(&self, writer: &mut dyn Write) -> SerializeResult<usize> {
+        self.write_der(writer)
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a, T> ToDer for &'a T
+where
+    T: ToDer,
+    &'a T: DynTagged,
+{
+    fn to_der_len(&self) -> Result<usize> {
+        (*self).to_der_len()
+    }
+
+    fn write_der_header(&self, writer: &mut dyn Write) -> SerializeResult<usize> {
+        (*self).write_der_header(writer)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn Write) -> SerializeResult<usize> {
+        (*self).write_der_content(writer)
+    }
+}
+
+/// Helper trait for creating tagged EXPLICIT values
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{AsTaggedExplicit, Class, Error, TaggedParser};
+///
+/// // create a `[1] EXPLICIT INTEGER` value
+/// let tagged: TaggedParser<_, _, Error> = 4u32.explicit(Class::ContextSpecific, 1);
+/// ```
+pub trait AsTaggedExplicit<'a, E = Error>: Sized {
+    fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E> {
+        TaggedParser::new_explicit(class, tag, self)
+    }
+}
+
+impl<'a, T, E> AsTaggedExplicit<'a, E> for T where T: Sized + 'a {}
+
+/// Helper trait for creating tagged IMPLICIT values
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{AsTaggedImplicit, Class, Error, TaggedParser};
+///
+/// // create a `[1] IMPLICIT INTEGER` value, not constructed
+/// let tagged: TaggedParser<_, _, Error> = 4u32.implicit(Class::ContextSpecific, false, 1);
+/// ```
+pub trait AsTaggedImplicit<'a, E = Error>: Sized {
+    fn implicit(
+        self,
+        class: Class,
+        constructed: bool,
+        tag: u32,
+    ) -> TaggedParser<'a, Implicit, Self, E> {
+        TaggedParser::new_implicit(class, constructed, tag, self)
+    }
+}
+
+impl<'a, T, E> AsTaggedImplicit<'a, E> for T where T: Sized + 'a {}
+
+pub trait ToStatic {
+    type Owned: 'static;
+    fn to_static(&self) -> Self::Owned;
+}
diff --git a/tests/ber.rs b/tests/ber.rs
new file mode 100644
index 0000000..d1c6705
--- /dev/null
+++ b/tests/ber.rs
@@ -0,0 +1,518 @@
+use asn1_rs::*;
+use hex_literal::hex;
+use nom::Needed;
+#[cfg(feature = "datetime")]
+use time::macros::datetime;
+
+#[test]
+fn from_ber_any() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = Any::from_ber(input).expect("parsing failed");
+    // dbg!(&result);
+    assert_eq!(rem, &[0xff, 0xff]);
+    assert_eq!(result.header.tag(), Tag::Integer);
+}
+
+#[test]
+fn from_ber_bitstring() {
+    //
+    // correct DER encoding
+    //
+    let input = &hex!("03 04 06 6e 5d c0");
+    let (rem, result) = BitString::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.unused_bits, 6);
+    assert_eq!(&result.data[..], &input[3..]);
+    //
+    // correct encoding, but wrong padding bits (not all set to 0)
+    //
+    let input = &hex!("03 04 06 6e 5d e0");
+    let (rem, result) = BitString::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.unused_bits, 6);
+    assert_eq!(&result.data[..], &input[3..]);
+    //
+    // long form of length (invalid, < 127)
+    //
+    let input = &hex!("03 81 04 06 6e 5d c0");
+    let (rem, result) = BitString::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.unused_bits, 6);
+    assert_eq!(&result.data[..], &input[4..]);
+}
+
+#[test]
+fn from_ber_embedded_pdv() {
+    let input = &hex!("2b 0d a0 07 81 05 2a 03 04 05 06 82 02 aa a0");
+    let (rem, result) = EmbeddedPdv::from_ber(input).expect("parsing failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(
+        result.identification,
+        PdvIdentification::Syntax(Oid::from(&[1, 2, 3, 4, 5, 6]).unwrap())
+    );
+    assert_eq!(result.data_value, &[0xaa, 0xa0]);
+}
+
+#[test]
+fn embedded_pdv_variants() {
+    // identification: syntaxes
+    let input = &hex!("2b 11 a0 0c a0 0a 80 02  2a 03 81 04 2a 03 04 05   82 01 00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(
+        res.identification,
+        PdvIdentification::Syntaxes { .. }
+    ));
+    // identification: syntax
+    let input = &hex!("2b 09 a0 04 81 02 2a 03  82 01 00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(res.identification, PdvIdentification::Syntax(_)));
+    // identification: presentation-context-id
+    let input = &hex!("2b 08 a0 03 82 01 02 82  01 00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(
+        res.identification,
+        PdvIdentification::PresentationContextId(_)
+    ));
+    // identification: context-negotiation
+    let input = &hex!("2b 10 a0 0b a3 09 80 01  2a 81 04 2a 03 04 05 82   01 00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(
+        res.identification,
+        PdvIdentification::ContextNegotiation { .. }
+    ));
+    // identification: transfer-syntax
+    let input = &hex!("2b 0b a0 06 84 04 2a 03  04 05 82 01 00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(
+        res.identification,
+        PdvIdentification::TransferSyntax(_)
+    ));
+    // identification: fixed
+    let input = &hex!("2b 07 a0 02 85 00 82 01  00");
+    let (rem, res) = EmbeddedPdv::from_ber(input).expect("parsing EMBEDDED PDV failed");
+    assert!(rem.is_empty());
+    assert!(matches!(res.identification, PdvIdentification::Fixed));
+    // identification: invalid
+    let input = &hex!("2b 07 a0 02 86 00 82 01  00");
+    let e = EmbeddedPdv::from_ber(input).expect_err("parsing should fail");
+    assert!(matches!(e, Err::Error(Error::InvalidValue { .. })));
+}
+
+#[test]
+fn from_ber_endofcontent() {
+    let input = &hex!("00 00");
+    let (rem, _result) = EndOfContent::from_ber(input).expect("parsing failed");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_ber_generalizedtime() {
+    let input = &hex!("18 0F 32 30 30 32 31 32 31 33 31 34 32 39 32 33 5A FF");
+    let (rem, result) = GeneralizedTime::from_ber(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff]);
+    #[cfg(feature = "datetime")]
+    {
+        let datetime = datetime! {2002-12-13 14:29:23 UTC};
+
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+    // local time with fractional seconds
+    let input = b"\x18\x1019851106210627.3";
+    let (rem, result) = GeneralizedTime::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.0.millisecond, Some(300));
+    assert_eq!(result.0.tz, ASN1TimeZone::Undefined);
+    #[cfg(feature = "datetime")]
+    {
+        let datetime = datetime! {1985-11-06 21:06:27.300_000_000 UTC};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+    // coordinated universal time with fractional seconds
+    let input = b"\x18\x1119851106210627.3Z";
+    let (rem, result) = GeneralizedTime::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.0.millisecond, Some(300));
+    assert_eq!(result.0.tz, ASN1TimeZone::Z);
+    #[cfg(feature = "datetime")]
+    {
+        let datetime = datetime! {1985-11-06 21:06:27.300_000_000 UTC};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+    // coordinated universal time with fractional seconds
+    let input = b"\x18\x1219851106210627.03Z";
+    let (rem, result) = GeneralizedTime::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.0.millisecond, Some(30));
+    assert_eq!(result.0.tz, ASN1TimeZone::Z);
+    #[cfg(feature = "datetime")]
+    {
+        let datetime = datetime! {1985-11-06 21:06:27.03 UTC};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+    // local time with fractional seconds, and with local time 5 hours retarded in relation to coordinated universal time.
+    let input = b"\x18\x1519851106210627.3-0500";
+    let (rem, result) = GeneralizedTime::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.0.millisecond, Some(300));
+    assert_eq!(result.0.tz, ASN1TimeZone::Offset(-5, 0));
+    #[cfg(feature = "datetime")]
+    {
+        let datetime = datetime! {1985-11-06 21:06:27.300_000_000 -05:00};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+}
+
+#[test]
+fn from_ber_int() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = u8::from_ber(input).expect("parsing failed");
+    assert_eq!(result, 2);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_ber_relative_oid() {
+    let input = &hex!("0d 04 c2 7b 03 02");
+    let (rem, result) = Oid::from_ber_relative(input).expect("parsing failed");
+    assert_eq!(result, Oid::from_relative(&[8571, 3, 2]).unwrap());
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_ber_length_incomplete() {
+    let input = &hex!("30");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Incomplete(Needed::new(1)));
+    let input = &hex!("02");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Incomplete(Needed::new(1)));
+    let input = &hex!("02 05");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Incomplete(Needed::new(5)));
+    let input = &hex!("02 85");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Incomplete(Needed::new(5)));
+    let input = &hex!("02 85 ff");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Incomplete(Needed::new(4)));
+}
+
+#[test]
+fn from_ber_length_invalid() {
+    let input = &hex!("02 ff 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Error(Error::InvalidLength));
+    let input = &hex!("02 85 ff ff ff ff ff 00");
+    let res = u8::from_ber(input).expect_err("parsing should have failed");
+    assert!(res.is_incomplete());
+}
+
+#[test]
+fn from_ber_octetstring() {
+    let input = &hex!("04 05 41 41 41 41 41");
+    let (rem, result) = OctetString::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), b"AAAAA");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_ber_real_binary() {
+    const EPSILON: f32 = 0.00001;
+    // binary, base = 2
+    let input = &hex!("09 03 80 ff 01 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::binary(1.0, 2, -1));
+    assert!((result.f32() - 0.5).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // binary, base = 2 and scale factor
+    let input = &hex!("09 03 94 ff 0d ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::binary(26.0, 2, -3).with_enc_base(8));
+    assert!((result.f32() - 3.25).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // binary, base = 16
+    let input = &hex!("09 03 a0 fe 01 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::binary(1.0, 2, -8).with_enc_base(16));
+    assert!((result.f32() - 0.00390625).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // binary, exponent = 0
+    let input = &hex!("09 03 80 00 01 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::binary(1.0, 2, 0));
+    assert!((result.f32() - 1.0).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // 2 octets for exponent and negative exponent
+    let input = &hex!("09 04 a1 ff 01 03 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::binary(3.0, 2, -1020).with_enc_base(16));
+    let epsilon = 1e-311_f64;
+    assert!((result.f64() - 2.67e-307).abs() < epsilon);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_ber_real_f32() {
+    const EPSILON: f32 = 0.00001;
+    // binary, base = 2
+    let input = &hex!("09 03 80 ff 01 ff ff");
+    let (rem, result) = <f32>::from_ber(input).expect("parsing failed");
+    assert!((result - 0.5).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_ber_real_f64() {
+    const EPSILON: f64 = 0.00001;
+    // binary, base = 2
+    let input = &hex!("09 03 80 ff 01 ff ff");
+    let (rem, result) = <f64>::from_ber(input).expect("parsing failed");
+    assert!((result - 0.5).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_ber_real_special() {
+    // 0
+    let input = &hex!("09 00 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::from(0.0));
+    assert_eq!(rem, &[0xff, 0xff]);
+    // infinity
+    let input = &hex!("09 01 40 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::Infinity);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // negative infinity
+    let input = &hex!("09 01 41 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::NegInfinity);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+#[allow(clippy::approx_constant)]
+fn from_ber_real_string() {
+    // text representation, NR3
+    let input = &hex!("09 07 03 33 31 34 45 2D 32 ff ff");
+    let (rem, result) = Real::from_ber(input).expect("parsing failed");
+    assert_eq!(result, Real::from(3.14));
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+#[allow(clippy::approx_constant)]
+fn from_ber_real_string_primitive() {
+    // text representation, NR3
+    let input = &hex!("09 07 03 33 31 34 45 2D 32 ff ff");
+    let (rem, result) = f32::from_ber(input).expect("parsing failed");
+    assert!((result - 3.14).abs() < 0.01);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_ber_sequence() {
+    let input = &hex!("30 05 02 03 01 00 01");
+    let (rem, result) = Sequence::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    //
+    let (_, i) = Sequence::from_ber_and_then(input, Integer::from_ber).expect("parsing failed");
+    assert_eq!(i.as_u32(), Ok(0x10001));
+}
+
+#[test]
+fn from_ber_sequence_vec() {
+    let input = &hex!("30 05 02 03 01 00 01");
+    let (rem, result) = <Vec<u32>>::from_ber(input).expect("parsing failed");
+    assert_eq!(&result, &[65537]);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_ber_sequence_of_vec() {
+    let input = &hex!("30 05 02 03 01 00 01");
+    let (rem, result) = <Sequence>::from_ber(input).expect("parsing failed");
+    let v = result
+        .ber_sequence_of::<u32, _>()
+        .expect("ber_sequence_of failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(&v, &[65537]);
+}
+
+#[test]
+fn from_ber_iter_sequence() {
+    let input = &hex!("30 0a 02 03 01 00 01 02 03 01 00 01");
+    let (rem, result) = Sequence::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let v = result
+        .ber_iter()
+        .collect::<Result<Vec<u32>>>()
+        .expect("could not iterate sequence");
+    assert_eq!(&v, &[65537, 65537]);
+}
+
+#[test]
+fn from_ber_set() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = Set::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    //
+    let (_, i) = Set::from_ber_and_then(input, Integer::from_ber).expect("parsing failed");
+    assert_eq!(i.as_u32(), Ok(0x10001));
+}
+
+#[test]
+fn from_ber_set_of_vec() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = <Set>::from_ber(input).expect("parsing failed");
+    let v = result.ber_set_of::<u32, _>().expect("ber_set_of failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(&v, &[65537]);
+}
+
+#[test]
+fn from_ber_iter_set() {
+    let input = &hex!("31 0a 02 03 01 00 01 02 03 01 00 01");
+    let (rem, result) = Set::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let v = result
+        .ber_iter()
+        .collect::<Result<Vec<u32>>>()
+        .expect("could not iterate set");
+    assert_eq!(&v, &[65537, 65537]);
+}
+
+#[test]
+fn from_ber_tag_custom() {
+    // canonical tag encoding
+    let input = &hex!("8f 02 12 34");
+    let (rem, any) = Any::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(any.tag(), Tag(15));
+    // non-canonical tag encoding
+    let input = &hex!("9f 0f 02 12 34");
+    let (rem, any) = Any::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(any.tag(), Tag(15));
+    assert_eq!(any.header.raw_tag(), Some(&[0x9f, 0x0f][..]));
+}
+
+#[test]
+fn from_ber_tag_incomplete() {
+    let input = &hex!("9f a2 a2");
+    let res = Any::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Error(Error::InvalidTag));
+}
+
+#[test]
+fn from_ber_tag_overflow() {
+    let input = &hex!("9f a2 a2 a2 a2 a2 a2 22 01 00");
+    let res = Any::from_ber(input).expect_err("parsing should have failed");
+    assert_eq!(res, nom::Err::Error(Error::InvalidTag));
+}
+
+#[test]
+fn from_ber_tag_long() {
+    let input = &hex!("9f a2 22 01 00");
+    let (rem, any) = Any::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(any.tag(), Tag(0x1122));
+    assert_eq!(any.header.raw_tag(), Some(&[0x9f, 0xa2, 0x22][..]));
+}
+
+#[test]
+fn from_ber_iter_sequence_incomplete() {
+    let input = &hex!("30 09 02 03 01 00 01 02 03 01 00");
+    let (rem, result) = Sequence::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let mut iter = result.ber_iter::<u32, Error>();
+    assert_eq!(iter.next(), Some(Ok(65537)));
+    assert_eq!(iter.next(), Some(Err(Error::Incomplete(Needed::new(1)))));
+    assert_eq!(iter.next(), None);
+}
+
+#[test]
+fn from_ber_set_of() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = SetOf::<u32>::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &[0x10001]);
+    assert_eq!(rem, &[]);
+    // not constructed
+    let input = &hex!("11 05 02 03 01 00 01");
+    let err = SetOf::<u32>::from_ber(input).expect_err("should have failed");
+    assert_eq!(err, Err::Error(Error::ConstructExpected));
+}
+
+#[test]
+fn from_ber_tagged_explicit_optional() {
+    let input = &hex!("a0 03 02 01 02");
+    let (rem, result) =
+        Option::<TaggedExplicit<u32, Error, 0>>::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(0));
+    assert_eq!(tagged.as_ref(), &2);
+    let (rem, result) =
+        Option::<TaggedExplicit<u32, Error, 1>>::from_ber(input).expect("parsing failed");
+    assert!(result.is_none());
+    assert_eq!(rem, input);
+
+    // using OptTaggedExplicit
+    let (rem, result) =
+        OptTaggedExplicit::<u32, Error, 0>::from_ber(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(0));
+    assert_eq!(tagged.as_ref(), &2);
+
+    // using OptTaggedParser
+    let (rem, result) = OptTaggedParser::from(0)
+        .parse_ber(input, |_, data| Integer::from_ber(data))
+        .expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result, Some(Integer::from(2)));
+}
+
+/// Generic tests on methods, and coverage tests
+#[test]
+fn from_ber_tagged_optional_cov() {
+    let p =
+        |input| OptTaggedParser::from(1).parse_ber::<_, Error, _>(input, |_, data| Ok((data, ())));
+    // empty input
+    let input = &[];
+    let (_, r) = p(input).expect("parsing failed");
+    assert!(r.is_none());
+    // wrong tag
+    let input = &hex!("a0 03 02 01 02");
+    let (_, r) = p(input).expect("parsing failed");
+    assert!(r.is_none());
+    // wrong class
+    let input = &hex!("e1 03 02 01 02");
+    let r = p(input);
+    assert!(r.is_err());
+}
+
+#[test]
+fn from_ber_universalstring() {
+    let input = &hex!("1C 10 00000061 00000062 00000063 00000064");
+    let (rem, result) = UniversalString::from_ber(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), "abcd");
+    assert_eq!(rem, &[]);
+}
diff --git a/tests/compile_tests.rs b/tests/compile_tests.rs
new file mode 100644
index 0000000..1491f57
--- /dev/null
+++ b/tests/compile_tests.rs
@@ -0,0 +1,6 @@
+#[test]
+fn compile_fail() {
+    let t = trybuild::TestCases::new();
+    t.pass("tests/run-pass/*.rs");
+    t.compile_fail("tests/compile-fail/*.rs");
+}
diff --git a/tests/cov.rs b/tests/cov.rs
new file mode 100644
index 0000000..374f950
--- /dev/null
+++ b/tests/cov.rs
@@ -0,0 +1,99 @@
+//! Generic and coverage tests
+use asn1_rs::*;
+use std::io;
+
+#[test]
+fn new_embedded_pdv() {
+    fn create_pdv(identification: PdvIdentification) -> EmbeddedPdv {
+        let pdv = EmbeddedPdv {
+            identification,
+            data_value_descriptor: None,
+            data_value: &[0x00, 0xff],
+        };
+        assert!(pdv.data_value_descriptor.is_none());
+        assert_eq!(pdv.data_value.len(), 2);
+        pdv
+    }
+    let identification = PdvIdentification::ContextNegotiation {
+        presentation_context_id: Integer::from(42_u8),
+        presentation_syntax: oid! { 1.2.3.4.5 },
+    };
+    let pdv1 = create_pdv(identification);
+    let identification = PdvIdentification::Syntaxes {
+        s_abstract: oid! { 1.2.3 },
+        s_transfer: oid! { 1.2.3.4.5 },
+    };
+    let pdv2 = create_pdv(identification);
+    assert!(pdv1 != pdv2);
+    let identification = PdvIdentification::Syntaxes {
+        s_abstract: oid! { 1.2.3 },
+        s_transfer: oid! { 1.2.3.4.5 },
+    };
+    let pdv3 = create_pdv(identification);
+    assert!(pdv3 == pdv2);
+}
+
+#[test]
+fn methods_error() {
+    let e = Error::invalid_value(Tag(0), "msg".to_string());
+    assert_eq!(
+        e,
+        Error::InvalidValue {
+            tag: Tag(0),
+            msg: "msg".to_string(),
+        }
+    );
+    //
+    let e = Error::unexpected_tag(None, Tag(0));
+    assert_eq!(
+        e,
+        Error::UnexpectedTag {
+            expected: None,
+            actual: Tag(0),
+        }
+    );
+    //
+    let e = Error::unexpected_class(None, Class::Application);
+    assert_eq!(
+        e,
+        Error::UnexpectedClass {
+            expected: None,
+            actual: Class::Application
+        }
+    );
+    //
+    use nom::error::ParseError;
+    let e = Error::from_error_kind(&[], nom::error::ErrorKind::Fail);
+    let e = <asn1_rs::Error as ParseError<_>>::append(&[], nom::error::ErrorKind::Eof, e);
+    let s = format!("{}", e);
+    assert!(s.starts_with("nom error:"));
+    //
+    let e1 = Error::from(nom::Err::Error(Error::BerTypeError));
+    let e2 = Error::from(nom::Err::Incomplete(nom::Needed::new(2)));
+    assert!(e1 != e2);
+    //
+    let e = SerializeError::from(Error::BerTypeError);
+    let s = format!("{}", e);
+    assert!(s.starts_with("ASN.1 error:"));
+    //
+    let e = SerializeError::InvalidClass { class: 4 };
+    let s = format!("{}", e);
+    assert!(s.starts_with("Invalid Class"));
+    //
+    let e = SerializeError::from(io::Error::new(io::ErrorKind::Other, "msg"));
+    let s = format!("{}", e);
+    assert!(s.starts_with("I/O error:"));
+}
+
+#[test]
+fn methods_tag() {
+    let t = Tag::from(2);
+    assert_eq!(t, Tag::Integer);
+    //
+    let err = t.invalid_value("test");
+    if let Error::InvalidValue { tag, .. } = err {
+        assert_eq!(tag, Tag::Integer);
+    } else {
+        unreachable!();
+    }
+}
diff --git a/tests/der.rs b/tests/der.rs
new file mode 100644
index 0000000..5fd87d7
--- /dev/null
+++ b/tests/der.rs
@@ -0,0 +1,649 @@
+use asn1_rs::*;
+use hex_literal::hex;
+use nom::sequence::pair;
+use nom::Needed;
+use std::collections::BTreeSet;
+use std::convert::TryInto;
+
+#[test]
+fn from_der_any() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = Any::from_der(input).expect("parsing failed");
+    // dbg!(&result);
+    assert_eq!(rem, &[0xff, 0xff]);
+    assert_eq!(result.header.tag(), Tag::Integer);
+}
+
+#[test]
+fn from_der_any_into() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = Any::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff, 0xff]);
+    assert_eq!(result.header.tag(), Tag::Integer);
+    let i: u32 = result.try_into().unwrap();
+    assert_eq!(i, 2);
+    //
+    let (_, result) = Any::from_der(input).expect("parsing failed");
+    let i = result.u32().unwrap();
+    assert_eq!(i, 2);
+}
+
+#[test]
+fn from_der_bitstring() {
+    //
+    // correct DER encoding
+    //
+    let input = &hex!("03 04 06 6e 5d c0");
+    let (rem, result) = BitString::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.unused_bits, 6);
+    assert_eq!(&result.data[..], &input[3..]);
+    //
+    // correct encoding, but wrong padding bits (not all set to 0)
+    //
+    let input = &hex!("03 04 06 6e 5d e0");
+    let res = BitString::from_der(input);
+    assert_eq!(
+        res,
+        Err(Err::Error(Error::DerConstraintFailed(
+            DerConstraint::UnusedBitsNotZero
+        )))
+    );
+    //
+    // long form of length (invalid, < 127)
+    //
+    // let input = &hex!("03 81 04 06 6e 5d c0");
+    // let res = BitString::from_der(input);
+    // assert_eq!(res, Err(Err::Error(Error::DerConstraintFailed)));
+}
+
+#[test]
+fn from_der_bitstring_constructed() {
+    let bytes: &[u8] = &hex!("23 81 0c 03 03 00 0a 3b 03 05 04 5f 29 1c d0");
+    assert_eq!(
+        BitString::from_der(bytes),
+        Err(Err::Error(Error::ConstructUnexpected))
+    );
+}
+
+#[test]
+fn from_der_bmpstring() {
+    // taken from https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-bmpstring
+    let input = &hex!("1e 08 00 55 00 73 00 65 00 72");
+    let (rem, result) = BmpString::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), "User");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_bool() {
+    let input = &hex!("01 01 00");
+    let (rem, result) = Boolean::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result, Boolean::FALSE);
+    //
+    let input = &hex!("01 01 ff");
+    let (rem, result) = Boolean::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result, Boolean::TRUE);
+    assert!(result.bool());
+    //
+    let input = &hex!("01 01 7f");
+    let res = Boolean::from_der(input);
+    assert_eq!(
+        res,
+        Err(Err::Error(Error::DerConstraintFailed(
+            DerConstraint::InvalidBoolean
+        )))
+    );
+    // bool type
+    let input = &hex!("01 01 00");
+    let (rem, result) = <bool>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(!result);
+}
+
+#[test]
+fn from_der_embedded_pdv() {
+    let input = &hex!("2b 0d a0 07 81 05 2a 03 04 05 06 82 02 aa a0");
+    let (rem, result) = EmbeddedPdv::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(
+        result.identification,
+        PdvIdentification::Syntax(Oid::from(&[1, 2, 3, 4, 5, 6]).unwrap())
+    );
+    assert_eq!(result.data_value, &[0xaa, 0xa0]);
+}
+
+#[test]
+fn from_der_enumerated() {
+    let input = &hex!("0a 01 02");
+    let (rem, result) = Enumerated::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(result.0, 2);
+}
+
+#[test]
+fn from_der_generalizedtime() {
+    let input = &hex!("18 0F 32 30 30 32 31 32 31 33 31 34 32 39 32 33 5A FF");
+    let (rem, result) = GeneralizedTime::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff]);
+    #[cfg(feature = "datetime")]
+    {
+        use time::macros::datetime;
+        let datetime = datetime! {2002-12-13 14:29:23 UTC};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result;
+    // local time with fractional seconds (should fail: no 'Z' at end)
+    let input = b"\x18\x1019851106210627.3";
+    let result = GeneralizedTime::from_der(input).expect_err("should not parse");
+    assert_eq!(
+        result,
+        nom::Err::Error(Error::DerConstraintFailed(DerConstraint::MissingTimeZone))
+    );
+    // coordinated universal time with fractional seconds
+    let input = b"\x18\x1119851106210627.3Z";
+    let (rem, result) = GeneralizedTime::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.0.millisecond, Some(300));
+    assert_eq!(result.0.tz, ASN1TimeZone::Z);
+    #[cfg(feature = "datetime")]
+    {
+        use time::macros::datetime;
+        let datetime = datetime! {1985-11-06 21:06:27.3 UTC};
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result.to_string();
+    // local time with fractional seconds, and with local time 5 hours retarded in relation to coordinated universal time.
+    // (should fail: no 'Z' at end)
+    let input = b"\x18\x1519851106210627.3-0500";
+    let result = GeneralizedTime::from_der(input).expect_err("should not parse");
+    assert_eq!(
+        result,
+        nom::Err::Error(Error::DerConstraintFailed(DerConstraint::MissingTimeZone))
+    );
+}
+
+#[test]
+fn from_der_indefinite_length() {
+    let bytes: &[u8] = &hex!("23 80 03 03 00 0a 3b 03 05 04 5f 29 1c d0 00 00");
+    assert_eq!(
+        BitString::from_der(bytes),
+        Err(Err::Error(Error::DerConstraintFailed(
+            DerConstraint::IndefiniteLength
+        )))
+    );
+    let bytes: &[u8] = &hex!("02 80 01 00 00");
+    assert!(Integer::from_der(bytes).is_err());
+}
+
+#[test]
+fn from_der_int() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = u8::from_der(input).expect("parsing failed");
+    assert_eq!(result, 2);
+    assert_eq!(rem, &[0xff, 0xff]);
+    // attempt to parse a value too large for container type
+    let input = &hex!("02 03 00 ff ff");
+    let err = u8::from_der(input).expect_err("parsing should fail");
+    assert_eq!(err, Err::Error(Error::IntegerTooLarge));
+    // attempt to parse a value too large (positive large value in signed integer)
+    let input = &hex!("02 03 00 ff ff");
+    let err = i16::from_der(input).expect_err("parsing should fail");
+    assert_eq!(err, Err::Error(Error::IntegerTooLarge));
+}
+
+#[test]
+fn from_der_null() {
+    let input = &hex!("05 00 ff ff");
+    let (rem, result) = Null::from_der(input).expect("parsing failed");
+    assert_eq!(result, Null {});
+    assert_eq!(rem, &[0xff, 0xff]);
+    // unit
+    let (rem, _unit) = <()>::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_der_octetstring() {
+    // coverage
+    use std::borrow::Cow;
+    let s = OctetString::new(b"1234");
+    assert_eq!(s.as_cow().len(), 4);
+    assert_eq!(s.into_cow(), Cow::Borrowed(b"1234"));
+    //
+    let input = &hex!("04 05 41 41 41 41 41");
+    let (rem, result) = OctetString::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), b"AAAAA");
+    assert_eq!(rem, &[]);
+    //
+    let (rem, result) = <&[u8]>::from_der(input).expect("parsing failed");
+    assert_eq!(result, b"AAAAA");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_octetstring_as_slice() {
+    let input = &hex!("04 05 41 41 41 41 41");
+    let (rem, result) = <&[u8]>::from_der(input).expect("parsing failed");
+    assert_eq!(result, b"AAAAA");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_oid() {
+    let input = &hex!("06 09 2a 86 48 86 f7 0d 01 01 05");
+    let (rem, result) = Oid::from_der(input).expect("parsing failed");
+    let expected = Oid::from(&[1, 2, 840, 113_549, 1, 1, 5]).unwrap();
+    assert_eq!(result, expected);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_optional() {
+    let input = &hex!("30 0a 0a 03 00 00 01 02 03 01 00 01");
+    let (rem, result) = Sequence::from_der_and_then(input, |input| {
+        let (i, obj0) = <Option<Enumerated>>::from_der(input)?;
+        let (i, obj1) = u32::from_der(i)?;
+        Ok((i, (obj0, obj1)))
+    })
+    .expect("parsing failed");
+    let expected = (Some(Enumerated::new(1)), 65537);
+    assert_eq!(result, expected);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_real_f32() {
+    const EPSILON: f32 = 0.00001;
+    // binary, base = 2
+    let input = &hex!("09 03 80 ff 01 ff ff");
+    let (rem, result) = <f32>::from_der(input).expect("parsing failed");
+    assert!((result - 0.5).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_der_real_f64() {
+    const EPSILON: f64 = 0.00001;
+    // binary, base = 2
+    let input = &hex!("09 03 80 ff 01 ff ff");
+    let (rem, result) = <f64>::from_der(input).expect("parsing failed");
+    assert!((result - 0.5).abs() < EPSILON);
+    assert_eq!(rem, &[0xff, 0xff]);
+}
+
+#[test]
+fn from_der_relative_oid() {
+    let input = &hex!("0d 04 c2 7b 03 02");
+    let (rem, result) = Oid::from_der_relative(input).expect("parsing failed");
+    let expected = Oid::from_relative(&[8571, 3, 2]).unwrap();
+    assert_eq!(result, expected);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_sequence() {
+    let input = &hex!("30 05 02 03 01 00 01");
+    let (rem, result) = Sequence::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_sequence_vec() {
+    let input = &hex!("30 05 02 03 01 00 01");
+    let (rem, result) = <Vec<u32>>::from_der(input).expect("parsing failed");
+    assert_eq!(&result, &[65537]);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_iter_sequence_parse() {
+    let input = &hex!("30 0a 02 03 01 00 01 02 03 01 00 01");
+    let (rem, result) = Sequence::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let (rem, v) = result
+        .parse(pair(u32::from_der, u32::from_der))
+        .expect("parse sequence data");
+    assert_eq!(v, (65537, 65537));
+    assert!(rem.is_empty());
+}
+#[test]
+fn from_der_iter_sequence() {
+    let input = &hex!("30 0a 02 03 01 00 01 02 03 01 00 01");
+    let (rem, result) = Sequence::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let v = result
+        .der_iter()
+        .collect::<Result<Vec<u32>>>()
+        .expect("could not iterate sequence");
+    assert_eq!(&v, &[65537, 65537]);
+}
+
+#[test]
+fn from_der_iter_sequence_incomplete() {
+    let input = &hex!("30 09 02 03 01 00 01 02 03 01 00");
+    let (rem, result) = Sequence::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let mut iter = result.der_iter::<u32, Error>();
+    assert_eq!(iter.next(), Some(Ok(65537)));
+    assert_eq!(iter.next(), Some(Err(Error::Incomplete(Needed::new(1)))));
+    assert_eq!(iter.next(), None);
+}
+
+#[test]
+fn from_der_set() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = Set::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    //
+    let (_, i) = Set::from_der_and_then(input, Integer::from_der).expect("parsing failed");
+    assert_eq!(i.as_u32(), Ok(0x10001));
+}
+
+#[test]
+fn from_der_set_btreeset() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = <BTreeSet<u32>>::from_der(input).expect("parsing failed");
+    assert!(result.contains(&65537));
+    assert_eq!(result.len(), 1);
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_set_of_vec() {
+    let input = &hex!("31 05 02 03 01 00 01");
+    let (rem, result) = <Set>::from_der(input).expect("parsing failed");
+    let v = result.der_set_of::<u32, _>().expect("ber_set_of failed");
+    assert_eq!(rem, &[]);
+    assert_eq!(&v, &[65537]);
+}
+
+#[test]
+fn from_der_iter_set() {
+    let input = &hex!("31 0a 02 03 01 00 01 02 03 01 00 01");
+    let (rem, result) = Set::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), &input[2..]);
+    assert_eq!(rem, &[]);
+    let v = result
+        .der_iter()
+        .collect::<Result<Vec<u32>>>()
+        .expect("could not iterate set");
+    assert_eq!(&v, &[65537, 65537]);
+}
+
+#[test]
+fn from_der_utctime() {
+    let input = &hex!("17 0D 30 32 31 32 31 33 31 34 32 39 32 33 5A FF");
+    let (rem, result) = UtcTime::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff]);
+    #[cfg(feature = "datetime")]
+    {
+        use time::macros::datetime;
+        let datetime = datetime! {2-12-13 14:29:23 UTC};
+
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result.to_string();
+    //
+    let input = &hex!("17 11 30 32 31 32 31 33 31 34 32 39 32 33 2b 30 33 30 30 FF");
+    let (rem, result) = UtcTime::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff]);
+    #[cfg(feature = "datetime")]
+    {
+        use time::macros::datetime;
+        let datetime = datetime! {2-12-13 14:29:23 +03:00};
+
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result.to_string();
+    //
+    let input = &hex!("17 11 30 32 31 32 31 33 31 34 32 39 32 33 2d 30 33 30 30 FF");
+    let (rem, result) = UtcTime::from_der(input).expect("parsing failed");
+    assert_eq!(rem, &[0xff]);
+    #[cfg(feature = "datetime")]
+    {
+        use time::macros::datetime;
+        let datetime = datetime! {2-12-13 14:29:23 -03:00};
+
+        assert_eq!(result.utc_datetime(), Ok(datetime));
+    }
+    let _ = result.to_string();
+}
+
+#[cfg(feature = "datetime")]
+#[test]
+fn utctime_adjusted_datetime() {
+    use time::macros::datetime;
+
+    let input = &hex!("17 0D 30 32 31 32 31 33 31 34 32 39 32 33 5A FF");
+    let (_, result) = UtcTime::from_der(input).expect("parsing failed");
+
+    assert_eq!(
+        result.utc_adjusted_datetime(),
+        Ok(datetime! {2002-12-13 14:29:23 UTC})
+    );
+
+    let input = &hex!("17 0D 35 30 31 32 31 33 31 34 32 39 32 33 5A FF");
+    let (_, result) = UtcTime::from_der(input).expect("parsing failed");
+
+    assert_eq!(
+        result.utc_adjusted_datetime(),
+        Ok(datetime! {1950-12-13 14:29:23 UTC})
+    );
+    let _ = result.to_string();
+}
+
+#[test]
+fn from_der_utf8string() {
+    let input = &hex!("0c 0a 53 6f 6d 65 2d 53 74 61 74 65");
+    let (rem, result) = Utf8String::from_der(input).expect("parsing failed");
+    assert_eq!(result.as_ref(), "Some-State");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_utf8string_as_str() {
+    let input = &hex!("0c 0a 53 6f 6d 65 2d 53 74 61 74 65");
+    let (rem, result) = <&str>::from_der(input).expect("parsing failed");
+    assert_eq!(result, "Some-State");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_utf8string_as_string() {
+    let input = &hex!("0c 0a 53 6f 6d 65 2d 53 74 61 74 65");
+    let (rem, result) = String::from_der(input).expect("parsing failed");
+    assert_eq!(&result, "Some-State");
+    assert_eq!(rem, &[]);
+}
+
+#[test]
+fn from_der_opt_int() {
+    let input = &hex!("02 01 02 ff ff");
+    let (rem, result) = <Option<u8>>::from_der(input).expect("parsing failed");
+    assert_eq!(result, Some(2));
+    assert_eq!(rem, &[0xff, 0xff]);
+    // non-fatal error
+    let (rem, result) = <Option<Ia5String>>::from_der(input).expect("parsing failed");
+    assert!(result.is_none());
+    assert_eq!(rem, input);
+    // fatal error (correct tag, but incomplete)
+    let input = &hex!("02 03 02 01");
+    let res = <Option<u8>>::from_der(input);
+    assert_eq!(res, Err(nom::Err::Incomplete(Needed::new(1))));
+}
+
+#[test]
+fn from_der_tagged_explicit() {
+    let input = &hex!("a0 03 02 01 02");
+    let (rem, result) = TaggedExplicit::<u32, Error, 0>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(0));
+    assert_eq!(result.as_ref(), &2);
+}
+
+#[test]
+fn from_der_tagged_explicit_with_class() {
+    let input = &hex!("a0 03 02 01 02");
+    // Note: the strange notation (using braces) is required by the compiler to use
+    // a constant instead of the numeric value.
+    let (rem, result) =
+        TaggedValue::<u32, Error, Explicit, { Class::CONTEXT_SPECIFIC }, 0>::from_der(input)
+            .expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(0));
+    assert_eq!(result.as_ref(), &2);
+}
+
+#[test]
+fn from_der_tagged_explicit_any_tag() {
+    let input = &hex!("a0 03 02 01 02");
+    let (rem, result) = TaggedParser::<Explicit, u32>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(0));
+    assert_eq!(result.as_ref(), &2);
+}
+
+#[test]
+fn from_der_tagged_explicit_optional() {
+    let input = &hex!("a0 03 02 01 02");
+    let (rem, result) =
+        Option::<TaggedExplicit<u32, Error, 0>>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(0));
+    assert_eq!(tagged.as_ref(), &2);
+    let (rem, result) =
+        Option::<TaggedExplicit<u32, Error, 1>>::from_der(input).expect("parsing failed");
+    assert!(result.is_none());
+    assert_eq!(rem, input);
+
+    // using OptTaggedExplicit
+    let (rem, result) =
+        OptTaggedExplicit::<u32, Error, 0>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(0));
+    assert_eq!(tagged.as_ref(), &2);
+
+    // using OptTaggedParser
+    let (rem, result) = OptTaggedParser::from(0)
+        .parse_der(input, |_, data| Integer::from_der(data))
+        .expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result, Some(Integer::from(2)));
+}
+
+#[test]
+fn from_der_tagged_implicit() {
+    let input = &hex!("81 04 70 61 73 73");
+    let (rem, result) = TaggedImplicit::<&str, Error, 1>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(1));
+    assert_eq!(result.as_ref(), &"pass");
+}
+
+#[test]
+fn from_der_tagged_implicit_with_class() {
+    let input = &hex!("81 04 70 61 73 73");
+    // Note: the strange notation (using braces) is required by the compiler to use
+    // a constant instead of the numeric value.
+    let (rem, result) =
+        TaggedValue::<&str, Error, Implicit, { Class::CONTEXT_SPECIFIC }, 1>::from_der(input)
+            .expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(1));
+    assert_eq!(result.as_ref(), &"pass");
+}
+
+#[test]
+fn from_der_tagged_implicit_any_tag() {
+    let input = &hex!("81 04 70 61 73 73");
+    let (rem, result) = TaggedParser::<Implicit, &str>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(1));
+    assert_eq!(result.as_ref(), &"pass");
+}
+
+#[test]
+fn from_der_tagged_implicit_optional() {
+    let input = &hex!("81 04 70 61 73 73");
+    let (rem, result) =
+        Option::<TaggedImplicit<&str, Error, 1>>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(1));
+    assert_eq!(tagged.as_ref(), &"pass");
+    let (rem, result) =
+        Option::<TaggedImplicit<&str, Error, 0>>::from_der(input).expect("parsing failed");
+    assert!(result.is_none());
+    assert_eq!(rem, input);
+
+    // using OptTaggedExplicit
+    let (rem, result) =
+        OptTaggedImplicit::<&str, Error, 1>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert!(result.is_some());
+    let tagged = result.unwrap();
+    assert_eq!(tagged.tag(), Tag(1));
+    assert_eq!(tagged.as_ref(), &"pass");
+}
+
+#[test]
+fn from_der_tagged_implicit_all() {
+    let input = &hex!("81 04 70 61 73 73");
+    let (rem, result) =
+        TaggedParser::<Implicit, Ia5String>::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    assert_eq!(result.tag(), Tag(1));
+    assert_eq!(result.as_ref().as_ref(), "pass");
+
+    // try the API verifying class and tag
+    let _ = TaggedParser::<Implicit, Ia5String>::parse_der(Class::ContextSpecific, Tag(1), input)
+        .expect("parsing failed");
+
+    // test TagParser API
+    let parser = TaggedParserBuilder::implicit()
+        .with_class(Class::ContextSpecific)
+        .with_tag(Tag(1))
+        .der_parser::<Ia5String>();
+    let _ = parser(input).expect("parsing failed");
+
+    // try specifying the expected tag (correct tag)
+    let _ = parse_der_tagged_implicit::<_, Ia5String, _>(1)(input).expect("parsing failed");
+    // try specifying the expected tag (incorrect tag)
+    let _ = parse_der_tagged_implicit::<_, Ia5String, _>(2)(input)
+        .expect_err("parsing should have failed");
+}
+
+/// Generic tests on methods, and coverage tests
+#[test]
+fn from_der_tagged_optional_cov() {
+    let p =
+        |input| OptTaggedParser::from(1).parse_der::<_, Error, _>(input, |_, data| Ok((data, ())));
+    // empty input
+    let input = &[];
+    let (_, r) = p(input).expect("parsing failed");
+    assert!(r.is_none());
+    // wrong tag
+    let input = &hex!("a0 03 02 01 02");
+    let (_, r) = p(input).expect("parsing failed");
+    assert!(r.is_none());
+    // wrong class
+    let input = &hex!("e1 03 02 01 02");
+    let r = p(input);
+    assert!(r.is_err());
+
+    let p = OptTaggedParser::from(Tag(1));
+    let _ = format!("{:?}", p);
+}
diff --git a/tests/krb5.rs b/tests/krb5.rs
new file mode 100644
index 0000000..d48ed2f
--- /dev/null
+++ b/tests/krb5.rs
@@ -0,0 +1,108 @@
+//! Test implementation for Kerberos v5
+//!
+//! This is mostly used to verify that required types and functions are implemented,
+//! and that provided API is convenient.
+
+use asn1_rs::*;
+use hex_literal::hex;
+
+const PRINCIPAL_NAME: &[u8] = &hex!("30 81 11 a0 03 02 01 00 a1 0a 30 81 07 1b 05 4a 6f 6e 65 73");
+
+/// PrincipalName   ::= SEQUENCE {
+///         name-type       [0] Int32,
+///         name-string     [1] SEQUENCE OF KerberosString
+/// }
+#[derive(Debug, PartialEq, Eq)]
+pub struct PrincipalName {
+    pub name_type: NameType,
+    pub name_string: Vec<String>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct NameType(pub i32);
+
+// KerberosString  ::= GeneralString (IA5String)
+pub type KerberosString<'a> = GeneralString<'a>;
+
+pub type KerberosStringList<'a> = Vec<KerberosString<'a>>;
+
+impl Tagged for PrincipalName {
+    const TAG: Tag = Tag::Sequence;
+}
+
+impl<'a> FromDer<'a> for PrincipalName {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        // XXX in the example above, PRINCIPAL_NAME does not respect DER constraints (length is using long form while < 127)
+        let (rem, seq) = Sequence::from_ber(bytes)?;
+        seq.and_then(|data| {
+            let input = &data;
+            let (i, t) = parse_der_tagged_explicit::<_, u32, _>(0)(input)?;
+            let name_type = t.inner;
+            let name_type = NameType(name_type as i32);
+            let (_, t) = parse_der_tagged_explicit::<_, KerberosStringList, _>(1)(i)?;
+            let name_string = t.inner.iter().map(|s| s.string()).collect();
+            Ok((
+                rem,
+                PrincipalName {
+                    name_type,
+                    name_string,
+                },
+            ))
+        })
+    }
+}
+
+impl ToDer for PrincipalName {
+    fn to_der_len(&self) -> Result<usize> {
+        let sz = self.name_type.0.to_der_len()? + 2 /* tagged */;
+        let sz = sz + self.name_string.to_der_len()? + 2 /* tagged */;
+        Ok(sz)
+    }
+
+    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        let len = self.to_der_len()?;
+        let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+        header.write_der_header(writer).map_err(Into::into)
+    }
+
+    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+        // build DER sequence content
+        let sz1 = self
+            .name_type
+            .0
+            .explicit(Class::ContextSpecific, 0)
+            .write_der(writer)?;
+        let sz2 = self
+            .name_string
+            .iter()
+            .map(|s| KerberosString::from(s.as_ref()))
+            .collect::<Vec<_>>()
+            .explicit(Class::ContextSpecific, 1)
+            .write_der(writer)?;
+        Ok(sz1 + sz2)
+    }
+}
+
+#[test]
+fn krb5_principalname() {
+    let input = PRINCIPAL_NAME;
+    let (rem, res) = PrincipalName::from_der(input).expect("parsing failed");
+    assert!(rem.is_empty());
+    let expected = PrincipalName {
+        name_type: NameType(0),
+        name_string: vec!["Jones".to_string()],
+    };
+    assert_eq!(res, expected);
+}
+
+#[test]
+fn to_der_krb5_principalname() {
+    let principal = PrincipalName {
+        name_type: NameType(0),
+        name_string: vec!["Jones".to_string()],
+    };
+    let v = PrincipalName::to_der_vec(&principal).expect("serialization failed");
+    std::fs::write("/tmp/out.bin", &v).unwrap();
+    let (_, principal2) = PrincipalName::from_der(&v).expect("parsing failed");
+    assert!(principal.eq(&principal2));
+}
diff --git a/tests/to_der.rs b/tests/to_der.rs
new file mode 100644
index 0000000..eea7560
--- /dev/null
+++ b/tests/to_der.rs
@@ -0,0 +1,515 @@
+use asn1_rs::*;
+use hex_literal::hex;
+// use nom::HexDisplay;
+use std::collections::BTreeSet;
+use std::convert::{TryFrom, TryInto};
+use std::iter::FromIterator;
+
+macro_rules! test_simple_string {
+    ($t:ty, $s:expr) => {
+        let t = <$t>::from($s);
+        let v = t.to_der_vec().expect("serialization failed");
+        assert_eq!(v[0] as u32, t.tag().0);
+        assert_eq!(v[1] as usize, t.as_ref().len());
+        assert_eq!(&v[2..], $s.as_bytes());
+        let (_, t2) = <$t>::from_der(&v).expect("decoding serialized object failed");
+        assert!(t.eq(&t2));
+    };
+}
+
+macro_rules! test_string_invalid_charset {
+    ($t:ty, $s:expr) => {
+        <$t>::test_valid_charset($s.as_bytes()).expect_err("should reject charset");
+    };
+}
+
+#[test]
+fn to_der_length() {
+    // indefinite length
+    let length = Length::Indefinite;
+    let v = length.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x80]);
+    // definite, short form
+    let length = Length::Definite(3);
+    let v = length.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x03]);
+    // definite, long form
+    let length = Length::Definite(250);
+    let v = length.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x81, 0xfa]);
+}
+
+#[test]
+fn to_der_length_long() {
+    let s = core::str::from_utf8(&[0x41; 256]).unwrap();
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..4], &[0x0c, 0x82, 0x01, 0x00]);
+    assert_eq!(&v[4..], s.as_bytes());
+}
+
+#[test]
+fn to_der_tag() {
+    // short tag, UNIVERSAL
+    let v = (Class::Universal, false, Tag(0x1a))
+        .to_der_vec()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0x1a]);
+    // short tag, APPLICATION
+    let v = (Class::Application, false, Tag(0x1a))
+        .to_der_vec()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0x1a | (0b01 << 6)]);
+    // short tag, constructed
+    let v = (Class::Universal, true, Tag(0x10))
+        .to_der_vec()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0x30]);
+    // long tag, UNIVERSAL
+    let v = (Class::Universal, false, Tag(0x1a1a))
+        .to_der_vec()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0b1_1111, 0x9a, 0x34]);
+}
+
+#[test]
+fn to_der_header() {
+    // simple header
+    let header = Header::new_simple(Tag::Integer);
+    let v = header.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x2, 0x0]);
+    // indefinite length
+    let header = Header::new(Class::Universal, false, Tag::Integer, Length::Indefinite);
+    let v = header.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x2, 0x80]);
+}
+
+#[test]
+fn to_der_any() {
+    let header = Header::new_simple(Tag::Integer);
+    let any = Any::new(header, &hex!("02"));
+    assert_eq!(any.to_der_len(), Ok(3));
+    let v = any.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x02, 0x01, 0x02]);
+}
+
+#[test]
+fn to_der_any_raw() {
+    let header = Header::new(Class::Universal, false, Tag::Integer, Length::Definite(3));
+    let any = Any::new(header, &hex!("02"));
+    // to_vec should compute the length
+    let v = any.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x02, 0x01, 0x02]);
+    // to_vec_raw will use the header as provided
+    let v = any.to_der_vec_raw().expect("serialization failed");
+    assert_eq!(&v, &[0x02, 0x03, 0x02]);
+}
+
+#[test]
+fn to_der_bitstring() {
+    let bitstring = BitString::new(6, &hex!("6e 5d c0"));
+    let v = bitstring.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("03 04 06 6e 5d c0"));
+    let (_, result) = BitString::from_der(&v).expect("parsing failed");
+    assert!(bitstring.eq(&result));
+}
+
+#[test]
+fn to_der_bmpstring() {
+    let bmpstring = BmpString::new("User");
+    assert_eq!(bmpstring.to_der_len(), Ok(10));
+    let v = bmpstring.to_der_vec().expect("serialization failed");
+    let expected = &hex!("1e 08 00 55 00 73 00 65 00 72");
+    assert_eq!(&v, expected);
+    assert!(BmpString::test_valid_charset(&v[2..]).is_ok());
+    let (_, result) = BmpString::from_der(&v).expect("parsing failed");
+    assert!(bmpstring.eq(&result));
+    // for coverage
+    let b1 = BmpString::from("s");
+    let s = b1.string();
+    let b2 = BmpString::from(s);
+    assert_eq!(b1, b2);
+    // long string
+    let sz = 256;
+    let s = str::repeat("a", sz);
+    let bmpstring = BmpString::new(&s);
+    assert_eq!(bmpstring.to_der_len(), Ok(4 + 2 * s.len()));
+    let _v = bmpstring.to_der_vec().expect("serialization failed");
+}
+
+#[test]
+fn to_der_bool() {
+    let v = Boolean::new(0xff)
+        .to_der_vec()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0x01, 0x01, 0xff]);
+    //
+    let v = false.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x01, 0x01, 0x00]);
+    //
+    let v = true.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x01, 0x01, 0xff]);
+    // raw value (not 0 of 0xff)
+    let v = Boolean::new(0x8a)
+        .to_der_vec_raw()
+        .expect("serialization failed");
+    assert_eq!(&v, &[0x01, 0x01, 0x8a]);
+}
+
+#[test]
+fn to_der_enumerated() {
+    let v = Enumerated(2).to_der_vec().expect("serialization failed");
+    assert_eq!(Enumerated(2).to_der_len(), Ok(3));
+    assert_eq!(&v, &[0x0a, 0x01, 0x02]);
+    //
+    let (_, result) = Enumerated::from_der(&v).expect("parsing failed");
+    assert_eq!(result, Enumerated(2));
+}
+
+#[test]
+fn to_der_generalizedtime() {
+    // date without millisecond
+    let dt = ASN1DateTime::new(1999, 12, 31, 23, 59, 59, None, ASN1TimeZone::Z);
+    let time = GeneralizedTime::new(dt);
+    let v = time.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..2], &hex!("18 0f"));
+    assert_eq!(&v[2..], b"19991231235959Z");
+    let (_, time2) = GeneralizedTime::from_der(&v).expect("decoding serialized object failed");
+    assert!(time.eq(&time2));
+    assert_eq!(time.to_der_len(), Ok(0x11));
+    //
+    // date with millisecond
+    let dt = ASN1DateTime::new(1999, 12, 31, 23, 59, 59, Some(123), ASN1TimeZone::Z);
+    let time = GeneralizedTime::new(dt);
+    let v = time.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..2], &hex!("18 13"));
+    assert_eq!(&v[2..], b"19991231235959.123Z");
+    let (_, time2) = GeneralizedTime::from_der(&v).expect("decoding serialized object failed");
+    assert!(time.eq(&time2));
+}
+
+#[test]
+fn to_der_graphicstring() {
+    test_simple_string!(GraphicString, "123456");
+    test_string_invalid_charset!(GraphicString, "é23456");
+}
+
+fn encode_decode_assert_int<T>(t: T, expected: &[u8])
+where
+    T: ToDer + std::fmt::Debug + Eq,
+    for<'a> T: TryFrom<Integer<'a>, Error = Error>,
+{
+    let v = t.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, expected);
+    let (_, obj) = Integer::from_der(&v).expect("decoding serialized object failed");
+    let t2: T = obj.try_into().unwrap();
+    assert_eq!(t, t2);
+}
+
+#[test]
+fn to_der_integer() {
+    let int = Integer::new(&hex!("02"));
+    let v = int.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x02, 0x01, 0x02]);
+    // from_u32
+    let int = Integer::from_u32(2);
+    let v = int.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &[0x02, 0x01, 0x02]);
+    // impl ToDer for primitive types
+    encode_decode_assert_int(2u32, &[0x02, 0x01, 0x02]);
+    // signed i32 (> 0)
+    encode_decode_assert_int(4, &[0x02, 0x01, 0x04]);
+    // signed i32 (< 0)
+    encode_decode_assert_int(-4, &[0x02, 0x01, 0xfc]);
+    // negative number
+    encode_decode_assert_int(-1i8, &[0x02, 0x01, 0xff]);
+}
+
+#[test]
+fn to_der_null() {
+    let bytes: &[u8] = &hex!("05 00");
+    let s = Null::new();
+    assert_eq!(s.to_der_len(), Ok(2));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, bytes);
+    // unit
+    assert_eq!(().to_der_len(), Ok(2));
+    let (_, s2) = <()>::from_der(&v).expect("decoding serialized object failed");
+    assert!(().eq(&s2));
+    let v2 = ().to_der_vec().expect("serialization failed");
+    assert_eq!(&v2, bytes);
+    // invalid null encodings
+    let bytes: &[u8] = &hex!("05 01 00");
+    let _ = Null::from_ber(bytes).expect_err("should fail");
+    let _ = <()>::from_ber(bytes).expect_err("should fail");
+}
+
+#[test]
+fn to_der_numericstring() {
+    test_simple_string!(NumericString, "123456");
+    test_string_invalid_charset!(NumericString, "abcdef");
+    test_string_invalid_charset!(NumericString, "1a");
+}
+
+#[test]
+fn to_der_objectdescriptor() {
+    test_simple_string!(ObjectDescriptor, "abcdef");
+    test_string_invalid_charset!(ObjectDescriptor, "abcdéf");
+}
+
+#[test]
+fn to_der_octetstring() {
+    let bytes: &[u8] = &hex!("01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f");
+    let s = OctetString::from(bytes);
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(s.to_der_len(), Ok(bytes.len() + 2));
+    assert_eq!(&v[..2], &hex!("04 0f"));
+    assert_eq!(&v[2..], bytes);
+    let (_, s2) = OctetString::from_der(&v).expect("decoding serialized object failed");
+    assert!(s.eq(&s2));
+    //
+    let v = bytes.to_der_vec().expect("serialization failed");
+    assert_eq!(bytes.to_der_len(), Ok(bytes.len() + 2));
+    assert_eq!(&v[..2], &hex!("04 0f"));
+    assert_eq!(&v[2..], bytes);
+    let (_, s2) = OctetString::from_der(&v).expect("decoding serialized object failed");
+    assert!(s.eq(&s2));
+}
+
+#[test]
+fn to_der_real_binary() {
+    // base = 2, value = 4
+    let r = Real::binary(2.0, 2, 1);
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 03 80 02 01"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!((r.f64() - result.f64()).abs() < f64::EPSILON);
+    //
+    // base = 2, value = 0.5
+    let r = Real::binary(0.5, 2, 0);
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 03 80 ff 01"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!((r.f64() - result.f64()).abs() < f64::EPSILON);
+    //
+    // base = 2, value = 3.25, but change encoding base (8)
+    let r = Real::binary(3.25, 2, 0).with_enc_base(8);
+    let v = r.to_der_vec().expect("serialization failed");
+    // note: this encoding has a scale factor (not DER compliant)
+    assert_eq!(&v, &hex!("09 03 94 ff 0d"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!((r.f64() - result.f64()).abs() < f64::EPSILON);
+    //
+    // base = 2, value = 0.00390625, but change encoding base (16)
+    let r = Real::binary(0.00390625, 2, 0).with_enc_base(16);
+    let v = r.to_der_vec().expect("serialization failed");
+    // note: this encoding has a scale factor (not DER compliant)
+    assert_eq!(&v, &hex!("09 03 a0 fe 01"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!((r.f64() - result.f64()).abs() < f64::EPSILON);
+    //
+    // 2 octets for exponent, negative exponent and abs(exponent) is all 1's and fills the whole octet(s)
+    let r = Real::binary(3.0, 2, -1020);
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 04 81 fc 04 03"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!((r.f64() - result.f64()).abs() < f64::EPSILON);
+    //
+    // 3 octets for exponent, and
+    // check that first 9 bits for exponent are not all 1's
+    let r = Real::binary(1.0, 2, 262140);
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 05 82 03 ff fc 01"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    // XXX value cannot be represented as f64 (inf)
+    assert!(result.f64().is_infinite());
+    //
+    // >3 octets for exponent, and
+    // mantissa < 0
+    let r = Real::binary(-1.0, 2, 76354972);
+    let v = r.to_der_vec().expect("serialization failed");
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert_eq!(&v, &hex!("09 07 c3 04 04 8d 15 9c 01"));
+    // XXX value cannot be represented as f64 (-inf)
+    assert!(result.f64().is_infinite());
+}
+
+#[test]
+fn to_der_real_special() {
+    // ZERO
+    let r = Real::Zero;
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 00"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!(r.eq(&result));
+    // INFINITY
+    let r = Real::Infinity;
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 01 40"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!(r.eq(&result));
+    // MINUS INFINITY
+    let r = Real::NegInfinity;
+    let v = r.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("09 01 41"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!(r.eq(&result));
+}
+
+#[test]
+fn to_der_real_string() {
+    //  non-zero value, base 10
+    let r = Real::new(1.2345);
+    let v = r.to_der_vec().expect("serialization failed");
+    // assert_eq!(&v, &hex!("09 00"));
+    let (_, result) = Real::from_der(&v).expect("parsing failed");
+    assert!(r.eq(&result));
+}
+
+#[test]
+fn to_der_sequence() {
+    let it = [2, 3, 4].iter();
+    let seq = Sequence::from_iter_to_der(it).unwrap();
+    let v = seq.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("30 09 02 01 02 02 01 03 02 01 04"));
+    let (_, seq2) = Sequence::from_der(&v).expect("decoding serialized object failed");
+    assert_eq!(seq, seq2);
+    // Vec<T>::ToDer
+    let v = vec![2, 3, 4].to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("30 09 02 01 02 02 01 03 02 01 04"));
+}
+
+#[test]
+fn to_der_set() {
+    let it = [2u8, 3, 4].iter();
+    let set = Set::from_iter_to_der(it).unwrap();
+    let v = set.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("31 09 02 01 02 02 01 03 02 01 04"));
+    // let (_, set2) = Set::from_der(&v).expect("decoding serialized object failed");
+    // assert_eq!(set, set2);
+    // BTreeSet<T>::ToDer
+    let set2 = BTreeSet::from_iter(vec![2, 3, 4]);
+    let v = set2.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("31 09 02 01 02 02 01 03 02 01 04"));
+}
+
+#[test]
+fn to_der_str() {
+    let s = "abcdef";
+    assert_eq!(s.to_der_len(), Ok(2 + s.len()));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..2], &hex!("0c 06"));
+    assert_eq!(&v[2..], b"abcdef");
+    let (_, s2) = Utf8String::from_der(&v).expect("decoding serialized object failed");
+    assert!(s.eq(s2.as_ref()));
+    // long string
+    let sz = 256;
+    let s = str::repeat("a", sz);
+    let s = s.as_str();
+    assert_eq!(s.to_der_len(), Ok(4 + sz));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(v.len(), 4 + sz);
+}
+
+#[test]
+fn to_der_string() {
+    let s = "abcdef".to_string();
+    assert_eq!(s.to_der_len(), Ok(2 + s.len()));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..2], &hex!("0c 06"));
+    assert_eq!(&v[2..], b"abcdef");
+    let (_, s2) = Utf8String::from_der(&v).expect("decoding serialized object failed");
+    assert!(s.eq(s2.as_ref()));
+    // long string
+    let sz = 256;
+    let s = str::repeat("a", sz);
+    assert_eq!(s.to_der_len(), Ok(4 + sz));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(v.len(), 4 + sz);
+}
+
+#[test]
+fn to_der_tagged_explicit() {
+    let tagged = TaggedParser::new_explicit(Class::ContextSpecific, 1, 2u32);
+    let v = tagged.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("a1 03 02 01 02"));
+    let (_, t2) =
+        TaggedParser::<Explicit, u32>::from_der(&v).expect("decoding serialized object failed");
+    assert!(tagged.eq(&t2));
+    // TaggedValue API
+    let tagged = TaggedValue::explicit(2u32);
+    let v = tagged.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("a1 03 02 01 02"));
+    let (_, t2) =
+        TaggedExplicit::<u32, Error, 1>::from_der(&v).expect("decoding serialized object failed");
+    assert!(tagged.eq(&t2));
+}
+
+#[test]
+fn to_der_tagged_implicit() {
+    let tagged = TaggedParser::new_implicit(Class::ContextSpecific, false, 1, 2u32);
+    let v = tagged.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("81 01 02"));
+    let (_, t2) =
+        TaggedParser::<Implicit, u32>::from_der(&v).expect("decoding serialized object failed");
+    assert!(tagged.eq(&t2));
+    // TaggedValue API
+    let tagged = TaggedValue::implicit(2u32);
+    let v = tagged.to_der_vec().expect("serialization failed");
+    assert_eq!(&v, &hex!("81 01 02"));
+    let (_, t2) =
+        TaggedImplicit::<u32, Error, 1>::from_der(&v).expect("decoding serialized object failed");
+    assert!(tagged.eq(&t2));
+}
+
+#[test]
+fn to_der_teletexstring() {
+    test_simple_string!(TeletexString, "abcdef");
+}
+
+#[test]
+fn to_der_utctime() {
+    let dt = ASN1DateTime::new(99, 12, 31, 23, 59, 59, None, ASN1TimeZone::Z);
+    let time = UtcTime::new(dt);
+    let v = time.to_der_vec().expect("serialization failed");
+    assert_eq!(&v[..2], &hex!("17 0d"));
+    assert_eq!(&v[2..], b"991231235959Z");
+    let (_, time2) = UtcTime::from_der(&v).expect("decoding serialized object failed");
+    assert!(time.eq(&time2));
+}
+
+#[test]
+fn to_der_universalstring() {
+    const S: &str = "abcdef";
+    let s = UniversalString::from(S);
+    assert_eq!(s.to_der_len(), Ok(2 + 4 * S.len()));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(
+        &v,
+        &hex!("1c 18 00000061 00000062 00000063 00000064 00000065 00000066")
+    );
+    let (_, s2) = UniversalString::from_der(&v).expect("decoding serialized object failed");
+    assert!(s.eq(&s2));
+    // long string
+    let sz = 256;
+    let s = str::repeat("a", sz);
+    let s = UniversalString::from(s);
+    assert_eq!(s.to_der_len(), Ok(4 + 4 * sz));
+    let v = s.to_der_vec().expect("serialization failed");
+    assert_eq!(v.len(), 4 + 4 * sz);
+}
+
+#[test]
+fn to_der_utf8string() {
+    test_simple_string!(Utf8String, "abcdef");
+}
+
+#[test]
+fn to_der_visiblestring() {
+    test_simple_string!(VisibleString, "abcdef");
+    test_string_invalid_charset!(VisibleString, "abcdéf");
+}
+
+#[test]
+fn to_der_videotexstring() {
+    test_simple_string!(VideotexString, "abcdef");
+}
diff --git a/tests/x509.rs b/tests/x509.rs
new file mode 100644
index 0000000..31fa595
--- /dev/null
+++ b/tests/x509.rs
@@ -0,0 +1,158 @@
+//! Test implementation for X.509
+//!
+//! This is mostly used to verify that required types and functions are implemented,
+//! and that provided API is convenient.
+
+use asn1_rs::{
+    nom, Any, CheckDerConstraints, Choice, Error, FromBer, FromDer, Oid, ParseResult, Sequence,
+    SetOf, Tag, Tagged,
+};
+use hex_literal::hex;
+use nom::sequence::pair;
+use std::convert::{TryFrom, TryInto};
+
+const DN: &[u8] = &hex!(
+    "
+30 45 31 0b 30 09 06 03 55 04 06 13 02 46 52
+31 13 30 11 06 03 55 04 08 0c 0a 53 6f 6d 65
+2d 53 74 61 74 65 31 21 30 1f 06 03 55 04 0a
+0c 18 49 6e 74 65 72 6e 65 74 20 57 69 64 67
+69 74 73 20 50 74 79 20 4c 74 64
+"
+);
+
+// Name ::= CHOICE { -- only one possibility for now --
+//     rdnSequence  RDNSequence }
+
+// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+#[derive(Debug)]
+pub struct Name<'a> {
+    pub rdn_sequence: Vec<RelativeDistinguishedName<'a>>,
+}
+
+impl<'a> FromDer<'a> for Name<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, rdn_sequence) = <Vec<RelativeDistinguishedName>>::from_der(bytes)?;
+        let dn = Name { rdn_sequence };
+        Ok((rem, dn))
+    }
+}
+
+// RelativeDistinguishedName ::=
+//     SET SIZE (1..MAX) OF AttributeTypeAndValue
+#[derive(Debug)]
+pub struct RelativeDistinguishedName<'a> {
+    pub v: Vec<AttributeTypeAndValue<'a>>,
+}
+
+impl<'a> FromDer<'a> for RelativeDistinguishedName<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, set) = SetOf::<AttributeTypeAndValue>::from_der(bytes)?;
+        let v: Vec<_> = set.into();
+        if v.is_empty() {
+            return Err(nom::Err::Failure(Error::InvalidLength));
+        }
+        Ok((rem, RelativeDistinguishedName { v }))
+    }
+}
+
+// AttributeTypeAndValue ::= SEQUENCE {
+//     type     AttributeType,
+//     value    AttributeValue }
+#[derive(Debug)]
+pub struct AttributeTypeAndValue<'a> {
+    pub oid: Oid<'a>,
+    pub value: AttributeValue<'a>,
+}
+
+impl<'a> FromBer<'a> for AttributeTypeAndValue<'a> {
+    fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, seq) = Sequence::from_der(bytes)?;
+        let (_, (oid, value)) =
+            seq.parse_into(|i| pair(Oid::from_der, AttributeValue::from_der)(i))?;
+        let attr = AttributeTypeAndValue { oid, value };
+        Ok((rem, attr))
+    }
+}
+
+impl<'a> FromDer<'a> for AttributeTypeAndValue<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, seq) = Sequence::from_der(bytes)?;
+        let (_, (oid, value)) =
+            seq.parse_into(|i| pair(Oid::from_der, AttributeValue::from_der)(i))?;
+        let attr = AttributeTypeAndValue { oid, value };
+        Ok((rem, attr))
+    }
+}
+
+impl<'a> CheckDerConstraints for AttributeTypeAndValue<'a> {
+    fn check_constraints(any: &Any) -> asn1_rs::Result<()> {
+        any.tag().assert_eq(Sequence::TAG)?;
+        Ok(())
+    }
+}
+
+// AttributeType ::= OBJECT IDENTIFIER
+
+// AttributeValue ::= ANY -- DEFINED BY AttributeType
+#[derive(Debug)]
+pub enum AttributeValue<'a> {
+    DirectoryString(DirectoryString),
+    Other(Any<'a>),
+}
+
+impl<'a> FromDer<'a> for AttributeValue<'a> {
+    fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+        let (rem, any) = Any::from_der(bytes)?;
+        let ds = if DirectoryString::can_decode(any.tag()) {
+            AttributeValue::DirectoryString(any.try_into()?)
+        } else {
+            AttributeValue::Other(any)
+        };
+        Ok((rem, ds))
+    }
+}
+
+// DirectoryString ::= CHOICE {
+//         teletexString           TeletexString (SIZE (1..MAX)),
+//         printableString         PrintableString (SIZE (1..MAX)),
+//         universalString         UniversalString (SIZE (1..MAX)),
+//         utf8String              UTF8String (SIZE (1..MAX)),
+//         bmpString               BMPString (SIZE (1..MAX)) }
+#[derive(Debug)]
+pub enum DirectoryString {
+    Printable(String),
+    Utf8(String),
+}
+
+impl Choice for DirectoryString {
+    fn can_decode(tag: Tag) -> bool {
+        matches!(tag, Tag::PrintableString | Tag::Utf8String)
+    }
+}
+
+impl<'a> TryFrom<Any<'a>> for DirectoryString {
+    type Error = Error;
+
+    fn try_from(any: Any<'a>) -> Result<Self, Self::Error> {
+        match any.tag() {
+            Tag::PrintableString => {
+                let s = any.printablestring()?;
+                Ok(DirectoryString::Printable(s.string()))
+            }
+            Tag::Utf8String => {
+                let s = any.string()?;
+                Ok(DirectoryString::Utf8(s))
+            }
+            _ => Err(Error::InvalidTag),
+        }
+    }
+}
+
+#[test]
+fn x509_decode_dn() {
+    let (rem, dn) = Name::from_der(DN).expect("parsing failed");
+    assert!(rem.is_empty());
+    // dbg!(&dn);
+    assert_eq!(dn.rdn_sequence.len(), 3);
+}