bouncycastle_core/suspendable_state.rs
1//! Helper functions for standardizing serialization and deserialization of stateful objects.
2
3use crate::errors::SuspendableError;
4
5/// A semantic library version, ordered by `major`, then `minor`, then `patch`.
6///
7/// The field declaration order matters: the derived [`Ord`]/[`PartialOrd`] compare fields
8/// lexicographically in declaration order, which is exactly semantic-version precedence.
9/// A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc.
10/// We're not going to model that here because it's not useful for versioning serialized states.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub struct SemVer {
13 ///
14 pub major: u8,
15 ///
16 pub minor: u8,
17 ///
18 pub patch: u8,
19 // A semantic version can often also take a suffix, e.g. "alpha", "beta", "rc1", etc.
20 // We're not going to model that here because it's not useful for versioning serialized states.
21}
22
23impl From<[u8; 3]> for SemVer {
24 fn from(v: [u8; 3]) -> Self {
25 SemVer { major: v[0], minor: v[1], patch: v[2] }
26 }
27}
28
29impl From<SemVer> for [u8; 3] {
30 fn from(v: SemVer) -> Self {
31 [v.major, v.minor, v.patch]
32 }
33}
34
35/// Parse a decimal ASCII string (a Cargo version component) into a u8 at compile time.
36const fn parse_version_component(s: &str) -> u8 {
37 let bytes = s.as_bytes();
38 let mut result: u8 = 0;
39 let mut i = 0;
40 while i < bytes.len() {
41 let d = bytes[i];
42 assert!(d >= b'0' && d <= b'9', "version component must be numeric");
43 // A component > 255 overflows u8 and fails the build (SemVer fields are u8 by design).
44 result = result * 10 + (d - b'0');
45 i += 1;
46 }
47 result
48}
49
50/// The current library version -- ie the version of the *bouncycastle-core* crate -- at compile time (via Cargo's
51/// `CARGO_PKG_VERSION_*` env vars).
52///
53/// MAINTAINER NOTE: this single value is the *only* compatibility gate for every serialized state in
54/// the workspace (see [`check_lib_ver`]), and the policy accepts any future *patch* on the same
55/// major.minor stream. Therefore any change to the on-the-wire layout of *any* suspendable state --
56/// in this crate or in any primitive crate -- MUST bump this crate's **minor** version (never just
57/// the patch), otherwise an older build will silently accept and misread a newer, incompatible state.
58/// Also keep this crate's version reconciled with the workspace release version so the stamp is
59/// meaningful.
60pub const LIB_VERSION: SemVer = SemVer {
61 major: parse_version_component(env!("CARGO_PKG_VERSION_MAJOR")),
62 minor: parse_version_component(env!("CARGO_PKG_VERSION_MINOR")),
63 patch: parse_version_component(env!("CARGO_PKG_VERSION_PATCH")),
64};
65
66#[test]
67fn test_cmp_lib_ver() {
68 use core::cmp::Ordering;
69
70 assert!([0, 0, 0] < [0, 0, 1]);
71
72 let cmp = |a: [u8; 3], b: [u8; 3]| SemVer::from(a).cmp(&SemVer::from(b));
73 assert_eq!(cmp([0, 2, 1], [1, 1, 1]), Ordering::Less);
74 assert_eq!(cmp([2, 1, 1], [1, 1, 1]), Ordering::Greater);
75 assert_eq!(cmp([1, 0, 2], [1, 1, 1]), Ordering::Less);
76 assert_eq!(cmp([1, 2, 0], [1, 1, 1]), Ordering::Greater);
77 assert_eq!(cmp([1, 1, 0], [1, 1, 1]), Ordering::Less);
78 assert_eq!(cmp([1, 1, 2], [1, 1, 1]), Ordering::Greater);
79 assert_eq!(cmp([1, 1, 1], [1, 1, 1]), Ordering::Equal);
80}
81
82/// Puts the library version into the first three bytes of the state array.
83///
84/// Hands back a slice to the same array, starting after the version tag.
85pub fn add_lib_ver<const SERIALIZED_LEN: usize>(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] {
86 state[..3].copy_from_slice(&<[u8; 3]>::from(LIB_VERSION));
87 &mut state[3..]
88}
89
90/// A helper for deserializing an object's state
91///
92/// The state_out array must have length at least SERIALIZED_LEN - 3.
93///
94/// Returns the number of bytes written to state_out, or a [`SuspendableError::IncompatibleVersion`] if
95/// the version of the serialized state is earlier than the specified `not_before` version, or
96/// is a future MAJOR or MINOR version (but future PATCH versions are ok).
97///
98/// Note that for testability, this will always reject if the serialized state contains a version tag
99/// of `[0,0,0]`.
100///
101/// Hands back a slice to the same array, starting after the version tag.
102pub fn check_lib_ver<const SERIALIZED_LEN: usize>(
103 state: &[u8; SERIALIZED_LEN],
104 not_before: Option<[u8; 3]>,
105) -> Result<&[u8], SuspendableError> {
106 // the .unwrap is infallible after the guard check
107 if state.len() < 3 {
108 return Err(SuspendableError::InvalidData);
109 }
110 let ver_bytes: [u8; 3] = state[..3].try_into().unwrap();
111 let ver = SemVer::from(ver_bytes);
112
113 let not_before = SemVer::from(not_before.unwrap_or([0, 0, 0]));
114
115 if ver < not_before {
116 return Err(SuspendableError::IncompatibleVersion);
117 };
118 // Nothing is ever compatible with [0,0,0]
119 if ver == SemVer::from([0, 0, 0]) {
120 return Err(SuspendableError::IncompatibleVersion);
121 };
122
123 // Check if state was produced by a later MAJOR or MINOR version;
124 // a future version on the same patch stream is ok (if not, then we've broken the rules of semantic versioning);
125 let patch_stream = SemVer::from([LIB_VERSION.major, LIB_VERSION.minor, 255]);
126 if ver > patch_stream {
127 return Err(SuspendableError::IncompatibleVersion);
128 }
129
130 Ok(&state[3..])
131}