detcore/
in_guest_launch.rs1const MAGIC: &[u8] = b"HERMIT-LITEINST-LAUNCH-V1\0";
20
21const FORWARD: &str = "forward";
22const POLICY: &str = "policy";
23const FINGERPRINT: &str = "fingerprint";
24
25#[derive(Clone, Debug, Default, PartialEq, Eq)]
27pub struct InGuestLaunch {
28 pub forward_request: Option<String>,
31 pub forward_policy: Option<String>,
33 pub coordinator_fingerprint: Option<String>,
35}
36
37impl InGuestLaunch {
38 pub fn encode(&self) -> Vec<u8> {
41 let mut bytes = MAGIC.to_vec();
42 for (name, value) in [
43 (FORWARD, &self.forward_request),
44 (POLICY, &self.forward_policy),
45 (FINGERPRINT, &self.coordinator_fingerprint),
46 ] {
47 if let Some(value) = value {
48 assert!(!value.contains('\0'), "{name} contains a NUL byte");
49 bytes.extend_from_slice(name.as_bytes());
50 bytes.push(b'=');
51 bytes.extend_from_slice(value.as_bytes());
52 bytes.push(0);
53 }
54 }
55 bytes
56 }
57
58 pub fn decode(bytes: &[u8]) -> Result<Self, String> {
62 let entries = bytes
63 .strip_prefix(MAGIC)
64 .ok_or("the tool data is not a Hermit LiteInst launch record")?;
65 let mut launch = Self::default();
66 let mut rest = entries;
67 while !rest.is_empty() {
68 let end = rest
69 .iter()
70 .position(|byte| *byte == 0)
71 .ok_or("a launch setting is not NUL-terminated")?;
72 let entry = std::str::from_utf8(&rest[..end])
73 .map_err(|_| "a launch setting is not UTF-8".to_owned())?;
74 rest = &rest[end + 1..];
75 let (name, value) = entry
76 .split_once('=')
77 .ok_or_else(|| format!("launch setting {entry:?} is not name=value"))?;
78 let slot = match name {
79 FORWARD => &mut launch.forward_request,
80 POLICY => &mut launch.forward_policy,
81 FINGERPRINT => &mut launch.coordinator_fingerprint,
82 _ => return Err(format!("unknown launch setting {name:?}")),
83 };
84 if slot.replace(value.to_owned()).is_some() {
85 return Err(format!("launch setting {name:?} is repeated"));
86 }
87 }
88 Ok(launch)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn a_launch_record_round_trips() {
98 for launch in [
99 InGuestLaunch::default(),
100 InGuestLaunch {
101 forward_request: Some("7:12345:67890".to_owned()),
102 forward_policy: Some("info,detcore::random=warn".to_owned()),
103 coordinator_fingerprint: Some("9349c85b2c48fc92".to_owned()),
104 },
105 InGuestLaunch {
106 coordinator_fingerprint: Some("0123456789abcdef".to_owned()),
107 ..Default::default()
108 },
109 ] {
110 assert_eq!(InGuestLaunch::decode(&launch.encode()), Ok(launch));
111 }
112 }
113
114 #[test]
115 fn a_launch_record_refuses_what_it_cannot_read() {
116 let mut repeated = MAGIC.to_vec();
117 repeated.extend_from_slice(b"policy=a\0policy=b\0");
118 let mut unknown = MAGIC.to_vec();
119 unknown.extend_from_slice(b"colour=blue\0");
120 let mut unterminated = MAGIC.to_vec();
121 unterminated.extend_from_slice(b"policy=a");
122 for bytes in [&b"REVERIE-LI-V1"[..], &repeated, &unknown, &unterminated] {
123 assert!(InGuestLaunch::decode(bytes).is_err(), "{bytes:?}");
124 }
125 }
126}