summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInna Palant <ipalant@google.com>2023-12-13 15:57:05 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-12-13 15:57:05 +0000
commite8337555de81f4b476cd5cd7735636937f49895b (patch)
treec45f74feaf5a4dc9defc073d4ed21cb2f44fb58d
parentb3f3469fe628561e9f1d9496515dd3f4fb2fb0bf (diff)
parentcc55ae95c7a2954e659cf27307b4996635c051b5 (diff)
downloadasn1-rs-e8337555de81f4b476cd5cd7735636937f49895b.tar.gz
Merge remote-tracking branch 'origin/upstream' am: cc55ae95c7
Original change: undetermined Change-Id: Ide052927a96539b1ff4a05dc4a21262e45989ae7 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--.gitignore2
-rw-r--r--CHANGELOG.md102
-rw-r--r--Cargo.toml127
-rw-r--r--Cargo.toml.orig66
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT25
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md179
-rw-r--r--doc/DERIVE.md323
-rw-r--r--doc/RECIPES.md238
-rw-r--r--examples/dump-der.rs243
-rw-r--r--src/asn1_types/any.rs429
-rw-r--r--src/asn1_types/bitstring.rs157
-rw-r--r--src/asn1_types/boolean.rs147
-rw-r--r--src/asn1_types/choice.rs21
-rw-r--r--src/asn1_types/embedded_pdv.rs125
-rw-r--r--src/asn1_types/end_of_content.rs55
-rw-r--r--src/asn1_types/enumerated.rs72
-rw-r--r--src/asn1_types/generalizedtime.rs303
-rw-r--r--src/asn1_types/integer.rs712
-rw-r--r--src/asn1_types/mod.rs26
-rw-r--r--src/asn1_types/null.rs99
-rw-r--r--src/asn1_types/object_descriptor.rs17
-rw-r--r--src/asn1_types/octetstring.rs157
-rw-r--r--src/asn1_types/oid.rs517
-rw-r--r--src/asn1_types/optional.rs87
-rw-r--r--src/asn1_types/real.rs462
-rw-r--r--src/asn1_types/real/f32.rs27
-rw-r--r--src/asn1_types/real/f64.rs27
-rw-r--r--src/asn1_types/sequence.rs398
-rw-r--r--src/asn1_types/sequence/iterator.rs106
-rw-r--r--src/asn1_types/sequence/sequence_of.rs150
-rw-r--r--src/asn1_types/sequence/vec.rs138
-rw-r--r--src/asn1_types/set.rs387
-rw-r--r--src/asn1_types/set/btreeset.rs124
-rw-r--r--src/asn1_types/set/hashset.rs125
-rw-r--r--src/asn1_types/set/iterator.rs22
-rw-r--r--src/asn1_types/set/set_of.rs150
-rw-r--r--src/asn1_types/strings.rs171
-rw-r--r--src/asn1_types/strings/bmpstring.rs132
-rw-r--r--src/asn1_types/strings/generalstring.rs14
-rw-r--r--src/asn1_types/strings/graphicstring.rs14
-rw-r--r--src/asn1_types/strings/ia5string.rs14
-rw-r--r--src/asn1_types/strings/numericstring.rs18
-rw-r--r--src/asn1_types/strings/printablestring.rs35
-rw-r--r--src/asn1_types/strings/str.rs67
-rw-r--r--src/asn1_types/strings/string.rs64
-rw-r--r--src/asn1_types/strings/teletexstring.rs18
-rw-r--r--src/asn1_types/strings/universalstring.rs137
-rw-r--r--src/asn1_types/strings/utf8string.rs13
-rw-r--r--src/asn1_types/strings/videotexstring.rs19
-rw-r--r--src/asn1_types/strings/visiblestring.rs18
-rw-r--r--src/asn1_types/tagged.rs128
-rw-r--r--src/asn1_types/tagged/application.rs42
-rw-r--r--src/asn1_types/tagged/builder.rs104
-rw-r--r--src/asn1_types/tagged/explicit.rs262
-rw-r--r--src/asn1_types/tagged/helpers.rs103
-rw-r--r--src/asn1_types/tagged/implicit.rs287
-rw-r--r--src/asn1_types/tagged/optional.rs240
-rw-r--r--src/asn1_types/tagged/parser.rs78
-rw-r--r--src/asn1_types/tagged/private.rs42
-rw-r--r--src/asn1_types/utctime.rs222
-rw-r--r--src/ber/mod.rs3
-rw-r--r--src/ber/parser.rs169
-rw-r--r--src/class.rs94
-rw-r--r--src/const_int.rs43
-rw-r--r--src/datetime.rs108
-rw-r--r--src/derive.rs322
-rw-r--r--src/doc/mod.rs7
-rw-r--r--src/error.rs201
-rw-r--r--src/header.rs490
-rw-r--r--src/length.rs180
-rw-r--r--src/lib.rs214
-rw-r--r--src/tag.rs74
-rw-r--r--src/traits.rs348
-rw-r--r--tests/ber.rs518
-rw-r--r--tests/compile_tests.rs6
-rw-r--r--tests/cov.rs99
-rw-r--r--tests/der.rs649
-rw-r--r--tests/krb5.rs108
-rw-r--r--tests/to_der.rs515
-rw-r--r--tests/x509.rs158
86 files changed, 13122 insertions, 0 deletions
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);
+}