ctl_component_info/
lib.rs1use 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 #[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#[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}