Skip to main content

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}