Skip to main content

ctl_component_info/
lib.rs

1//! Embedded identity shared by components built from the same Rust sources.
2
3use serde::{Deserialize, Serialize};
4
5#[cfg(feature = "executable")]
6pub mod executable;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct ProtocolInfo {
10  pub name: String,
11  pub version: u16,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct ComponentInfo {
16  pub build: ComponentBuildInfo,
17  pub protocols: Vec<ProtocolInfo>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub struct ComponentBuildInfo {
22  pub version: String,
23  pub source_revision: Option<String>,
24  pub source_fingerprint: String,
25  pub dirty: bool,
26}
27
28impl ComponentBuildInfo {
29  /// Validates bounded metadata received across a process boundary.
30  #[must_use]
31  pub fn is_valid(&self) -> bool {
32    let hex = |value: &str| value.bytes().all(|byte| byte.is_ascii_hexdigit());
33    !self.version.is_empty()
34      && self.version.len() <= 256
35      && !self.version.chars().any(char::is_control)
36      && self.source_fingerprint.len() == 64
37      && hex(&self.source_fingerprint)
38      && self
39        .source_revision
40        .as_ref()
41        .is_none_or(|revision| matches!(revision.len(), 40 | 64) && hex(revision))
42  }
43}
44
45/// Returns compile-time metadata; never inspects a replacement executable.
46#[must_use]
47pub fn build_info() -> ComponentBuildInfo {
48  ComponentBuildInfo {
49    version: env!("CARGO_PKG_VERSION").into(),
50    source_revision: match env!("COMPONENT_SOURCE_REVISION") {
51      "" => None,
52      revision => Some(revision.into()),
53    },
54    source_fingerprint: env!("COMPONENT_SOURCE_FINGERPRINT").into(),
55    dirty: env!("COMPONENT_SOURCE_DIRTY") == "true",
56  }
57}
58
59#[cfg(test)]
60mod tests {
61  #[test]
62  fn identity_contains_a_deterministic_source_digest() {
63    let info = super::build_info();
64    assert_eq!(info.source_fingerprint.len(), 64);
65    assert!(
66      info
67        .source_fingerprint
68        .bytes()
69        .all(|byte| byte.is_ascii_hexdigit())
70    );
71    assert_eq!(info, super::build_info());
72    assert!(info.is_valid());
73  }
74
75  #[test]
76  fn malformed_metadata_cannot_claim_a_valid_component_identity() {
77    let mut info = super::build_info();
78    info.source_revision = None;
79    assert!(info.is_valid());
80    info.source_fingerprint = "invalid".into();
81    assert!(!info.is_valid());
82    info = super::build_info();
83    info.version.push('\n');
84    assert!(!info.is_valid());
85    info = super::build_info();
86    info.source_revision = Some("unverified-revision".into());
87    assert!(!info.is_valid());
88  }
89}