kmip_ttlv/
lib.rs

1//! A crate to (de)serialize Rust data types from/to bytes in the KMIP TTLV format.
2//!
3//! This is the detailed API documentation. For a higher level introduction see the [README].
4//!
5//! [README]: https://crates.io/crates/kmip-ttlv/
6//!
7//! Note that this crate only supports (de)serialization of primitive TTLV types, it does **NOT** send or receive data.
8//! See the [kmip-protocol](https://crates.io/crates/kmip-protocol/) crate for support for (de)serializing KMIP
9//! specification defined objects composed from TTLV primitives and for an example TLS client.
10//!
11//! # Usage, features and APIs
12//!
13//! Add the following to your `Cargo.toml`:
14//!
15//! ```toml
16//! [dependencies]
17//! kmip-ttlv = "0.3.5"
18//! serde = "1.0.126"
19//! serde_derive = "1.0.126"
20//! ```
21//!
22//! ## High level API
23//!
24//! Assuming that you have already defined your Rust types with the required attributes (more on this below) you can
25//! serialize and deserialize them using the high level Serde Derive based API as follows:
26//!
27//! ```ignore
28//! use kmip_ttlv::{Config, from_slice, to_vec};
29//!
30//! // Serialize some struct variable (whose type is correctly
31//! // attributed) to bytes in TTLV format:
32//! let bytes = to_vec(&my_struct)?;
33//!
34//! // Deserialize the byte vec back to a struct:
35//! let my_other_struct: MyStruct = from_slice(&mut bytes)?;
36//! ```
37//!
38//! ## Low level API
39//!
40//! There is also a low-level API which is much more labourious to use. The high-level API should be
41//! sufficient unless you wish to avoid depending on the Serde crates. You can disable the dependence on Serde by
42//! setting `default-features = false` in `Cargo.toml`, e.g.:
43//!
44//! ```toml
45//! [dependencies]
46//! kmip-ttlv = { version = "0.3.1", default-features = false }
47//! ```
48//!
49//! To learn more about the low-level API see the [types] module.
50//!
51//! ## Async API
52//!
53//! This crate also supports _deserialization_ from an async reader via the feature flags `async-with-async-std` and
54//! `async-with-tokio`. Only one of these flags can be specified at once and neither can be mixed with the default
55//! 'sync' feature flag. The example below also enables the high level API which is disabled otherwise when you
56//! use `default-features = false`.
57//!
58//! ```toml
59//! [dependencies.kmip-ttlv]
60//! version = "0.3.1"
61//! default-features = false
62//! features = ["async-with-async-std", "high-level"]
63//! ```
64//!
65//! Without an async feature enabled you can only pass something that implements the `Read` trait to [de::from_reader].
66//!
67//! With an async feature enabled you can pass something that implements `async_std::io::ReadExt` or
68//! `tokio::io::AsyncReadExt`. You'll also need to then suffix the call to [de::from_reader] with `.await` and call
69//! it from an `async` function or block.
70//!
71//! # TTLV format
72//!
73//! TTLV stands for Tag-Type-Length-Value which represents the format of each node in a tree when serialized to bytes:
74//!
75//!   - The TTLV format is defined as part of the [Oasis Key Management Interoperability Protocol Specification Version
76//!     1.0] (aka KMIP) in [Section 9.1 TTLV Encoding].
77//!   - The byte representation of a TTLV item consists of a 3 byte tag, a 1 byte type, a 4 byte length followed by zero
78//!     or more "Value" bytes.
79//!   - Leaf nodes in the tree are TTLV items whose "Type" denotes them to be a primitive value of some kind (e.g.
80//!     Integer, Boolean, etc) and whose "Value" is a single primitive value in serialized form, followed by any
81//!     required padding bytes.
82//!   - All other tree nodes are "Structure" TTLV items whose value consists of zero or more TTLV items.
83//!  
84//! Think of a TTLV "Structure" item as a Rust struct and all other TTLV items as fields within that struct but, unlike
85//! Rust data types which have a string name, TTLV items are identified by a numeric "Tag".
86//!
87//! [Oasis Key Management Interoperability Protocol Specification Version 1.0]: https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html
88//! [Section 9.1 TTLV Encoding]: https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581260
89//!
90//! # Mapping names to tags
91//!
92//! Rust identifies structs and struct fields by name but TTLV identifies items by numeric "Tag". We must therefore
93//! provide a way to map from name to tag and vice versa. As this crate is Serde (Derive) based we can take advantage of
94//! the [Serde Derive atribute] `#[serde(rename = "...")]` to handle this for us:
95//!
96//! [Serde Derive attribute]: https://serde.rs/attributes.html
97//!
98//! ```ignore
99//! use serde_derive::Serialize;
100//!
101//! #[derive(Serialize)]
102//! #[serde(rename = "0x123456")]
103//! struct MyTtlv { }
104//!
105//! println!("{:0X?}", kmip_ttlv::to_vec(&MyTtlv {}));
106//!
107//! // prints:
108//! // Ok([12, 34, 56, 1, 0, 0, 0, 0])
109//! ```
110//!
111//! You can see the TTLV byte format here: a 3 byte "tag", a 1 byte "type" (type code 1 means a TTLV Structure) and
112//! a 4 byte "length". There is no "value" part in this case because the struct doesn't have any fields so the value
113//! length is zero.
114//!
115//! > **NOTE:** If we omit the `#[serde(rename = "...")]` attribute this code will print an error.
116//!
117//! # Choosing tag values
118//!
119//! When implementing one of the KMIP specifications the tag value to use for each KMIP object is defined by the spec.
120//! The KMIP specifications reserve tag value range 0x420000 - 0x42FFFF for official KMIP tags and reserve tag value
121//! range 0x540000 - 0x54FFFF for custom extensions. If using TTLV as a serialization format for your own data you are
122//! free to choose your own tag values anywhere in the range 0x000000 - 0xFFFFFF.
123//!
124//! # Supported data types
125//!
126//! The following gives a rough indication of the mapping of TTLV types to Rust types by this crate and vice versa:
127//!
128//! | TTLV data type      | Serializes from     | Deserializes to     |
129//! |---------------------|---------------------|---------------------|
130//! | Structure (0x01)    | `SomeStruct { .. }`, `SomeStruct( .. )`, tuple variant | `SomeStruct { .. }` |
131//! | Integer (0x02)      | `i8`, `i16`, `i32`  | `i32`               |
132//! | Long Integer (0x03) | `i64`               | `i64`               |
133//! | Big Integer (0x04)  | **UNSUPPORTED**     | `Vec<u8>`           |
134//! | Enumeration (0x05)  | `u32`               | See above           |
135//! | Boolean (0x06)      | `bool`              | `bool`              |
136//! | Text String (0x07)  | `str``              | `String`            |
137//! | Byte String (0x08)  | `&[u8]`             | `Vec<u8>`           |
138//! | Date Time (0x09)    | `u64`               | `i64`               |
139//! | Interval (0x0A)     | **UNSUPPORTED**     | **UNSUPPORTED**     |
140//!
141//! # Unsupported data types
142//!
143//! Not all Rust and TTLV data types are supported by this crate, either because there is no obvious mapping from one to
144//! the other or because support for it wasn't needed yet:
145//!
146//! - The following Rust types **CANNOT** be _serialized_ to TTLV as TTLV has no concept of unsigned
147//!   integers, floating point, character or 'missing' values : `u8`, `u16`, `f32`, `f64`, `char`, `()`, `None` _(but
148//!   see below for a special note about `None`)_.
149//!
150//! - The following Rust types **CANNOT** be _deserialized_ from TTLV: `()`, `u8`, `u16`, `u32`, `u64`, `i8`, `i16`,
151//!  `f32`, `f64`, `char`, `str`, map, `&[u8]`, `()`. `char`,
152//!
153//! - The following TTLV types **CANNOT** _yet_ be serialized to TTLV: Big Integer (0x04), Interval (0x0A).
154//!
155//! - The following TTLV types **CANNOT** _yet_ be deserialized from TTLV: Interval (0x0A).
156//!
157//! - The following Rust types **CANNOT** be deserialized as this crate is opinionated and prefers to
158//!   deserialize only into named fields, not nameless groups of values: unit struct, tuple struct, tuple.
159//!
160//! # Data types treated specially
161//!
162//! - The Rust `struct` type by default serializes to a TTLV Structure However sometimes it is useful to be able to use a
163//!   newtype struct as a wrapper around a primitive type so that you can associate a TTLV tag value with it. This can be
164//!   done by using the `Transparent:` prefix when renaming the type, e.g. `#[serde(rename = "Transparent:0xNNNNNN")]`.
165//!
166//! - The Rust `Some` type is handled as if it were only the value inside the Option, the `Some` wrapper is ignored.
167//!
168//! - The Rust `None` type cannot be serialized to TTLV. Instead use `#[serde(skip_serializing_if = "Option::is_none")]`
169//!   on the `Option` field to be serialized so that Serde skips it if it has value `None` when serializing. When
170//!   deserializing into an `Option` if no value with the specified tag is present in the TTLV bytes the Option will be
171//!   set to `None`.
172//!
173//! - The Rust `Vec` type can be used to (de)serialize sequences of TTLV items. To serialize a `Vec` of bytes to a TTLV
174//!   Byte String however you should annotate the field with the Serde derive attribute `#[serde(with = "serde_bytes")]`.
175//!
176//! - The Rust `enum` type is serialized differently depending on the type of the variant being serialized. For unit
177//!   variants a `#[serde(rename = "0xNNNNNNNN")]` attribute should be used to cause this crate to serialize the value
178//!   as a TTLV Enumeration. A tuple or struct variant will be serialized to a TTLV Structure.
179//!
180//! - In order to _deserialize_ into a Rust `enum` you must guide this crate to the correct variant to deserialize into.
181//!   To support the KMIP specifications this crate supports choosing the variant based on the value of a TTLV item that
182//!   was encountered earlier in the deserialization process. To handle this case each candidate `enum` variant must be
183//!   specially renamed with Serde derive using one of several supported special matcher syntaxes:
184//!
185//!   - `#[serde(rename = "if 0xNNNNNN==0xMMMMMMMM")]` syntax will cause this crate to look for a previously encountered
186//!     TTLV Enumeration with tag value 0xNNNNNN and to select this `enum` variant if that Enumeration had value
187//!     0xMMMMMMMM.
188//!   - `#[serde(rename = "if 0xNNNNNN in [0xAAAAAAAA, 0xBBBBBBBB, ..]")]` is like the previous syntax but can match
189//!     against more than one possible value.
190//!   - `#[serde(rename = "if 0xNNNNNN >= 0xMMMMMMMM")]` can be used to select the variant if a previously seen value
191//!     for the specified tag was at least the given value.
192//!   - `#[serde(rename = "if 0xNNNNNN==Textual Content")]` syntax will cause this crate to look for a previously
193//!     encountered TTLV Text String with tag value 0xNNNNNN and to select this `enum` variant if that Text String had
194//!     value `Textual Content`.
195//!   - `#[serde(rename = "if type==XXX")]` syntax (where `XXX` is a camel case TTLV type name without spaces such as
196//!     `LongInteger`) will cause this crate to select the enum variant if the TTLV type encountered while deserializing
197//!     has the specified type.
198//!
199//! - TTLV Big Integer values can be deserialized to a `Vec<u8>` in their raw byte format. Using a crate like
200//!   `num_bigint` you can work with these byte sequences as if they were normal Rust integers. For example, To convert
201//!   from a `Vec<u8>` obtained from a TTLV Big Integer to a `num_bigint::BigInt` use the
202//!   `num_bigint::BigInt::from_signed_bytes_be` function.
203//!
204//! # Examples
205//!
206//! For detailed examples of how to annotate your data types with Serde derive attributes for use with this crate look
207//! at the [tests in the source repository for this crate](https://github.com/NLnetLabs/kmip-ttlv/tree/main/src/tests/).
208//!
209//! For much richer examples see the code and tests in the source repository for the
210//! [kmip-protocol](https://crates.io/crates/kmip-protocol/) crate.
211//!
212//! The `examples/` folder contains a simple `hex_to_txt` tool which can pretty print a human readable tree structure
213//! form of the given hexadecimal encoded TTLV bytes. You can run the example with the command:
214//!
215//! ```bash
216//! cargo run --example hex_to_txt </path/to/hex_string_input_file>
217//! ```
218//!
219//! The tool will ignore any line breaks, spaces, double quotes and commas that are present in the file. Try it out by
220//! copying the quoted hex input and output strings in the [tests for this crate](https://github.com/NLnetLabs/kmip-ttlv/tree/main/src/tests/)
221//! to a file and passing that file to the `hex_to_txt` tool.
222//!
223//! # Error handling
224//!
225//! By default Serde ignores any items present in the TTLV byte stream that do not correspond to a tagged field in the
226//! Rust struct being deserialized into. You can disable this behaviour and make the presence of unexpected TTLV items
227//! into a deserialization error by using the `#[serde(deny_unknown_fields)]` container level Serde derive attribute.
228//! You can also explicitly ignore an unsupported item by using the `#[serde(skip_deserializing)]` field level
229//! attribute.
230//!
231//! This crate does not try to be clone free or to support `no_std` scenarios. Memory is allocated to serialize and
232//! deserialize into. In particular when deserializing bytes received from an untrusted source with `from_reader()` this
233//! could cause allocation of a large amount of memory at which point Rust will panic if the allocation fails. When
234//! deserializing with `from_reader()` you are strongly advised to use a `Config` object that specifies a maximum byte
235//! length to deserialize to prevent such abuse.
236//!
237//! If serialization or deserialization fails this crate tries to return sufficient contextual information to aid
238//! diagnosing where the problem in the data is and why.
239//!
240//! For logging or storing of requests and responses for later diagnostic purposes use the
241//! [PrettyPrinter::to_diag_string()] function to render TTLV bytes in a compact textual representation with most
242//! values redacted (only enumeration values are included in the generated string).
243#[cfg(all(
244    feature = "sync",
245    any(feature = "async-with-async-std", feature = "async-with-tokio")
246))]
247compile_error!("feature \"sync\" cannot be enabled at the same time as either of the \"async-with-async-std\" or \"async-with-tokio\" features");
248
249#[cfg(all(feature = "async-std", not(feature = "async-with-async-std")))]
250compile_error!("do not enable the \"async-std\" feature directly, instead enable the \"async-with-async-std\" feature");
251
252#[cfg(all(feature = "tokio", not(feature = "async-with-tokio")))]
253compile_error!("do not enable the \"tokio\" feature directly, instead enable the \"async-with-tokio\" feature");
254
255#[cfg(feature = "high-level")]
256#[macro_use]
257mod macros;
258
259#[cfg(feature = "high-level")]
260pub mod de;
261#[cfg(feature = "high-level")]
262pub mod error;
263#[cfg(feature = "high-level")]
264pub mod ser;
265#[cfg(feature = "high-level")]
266pub mod traits;
267pub mod types;
268#[cfg(feature = "high-level")]
269pub mod util;
270
271#[cfg(feature = "high-level")]
272#[doc(inline)]
273pub use de::{from_reader, from_slice, Config};
274
275#[cfg(feature = "high-level")]
276#[doc(inline)]
277pub use ser::{to_vec, to_writer};
278
279#[cfg(feature = "high-level")]
280#[doc(inline)]
281pub use util::PrettyPrinter;
282
283#[cfg(test)]
284mod tests;