1#[cfg(feature = "test-utils")]
13use std::sync::LazyLock;
14
15use lexe_byte_array::ByteArray;
16
17use crate::types::Measurement;
18
19pub fn version() -> Option<&'static str> {
21 cfg_if::cfg_if! {
22 if #[cfg(feature = "test-utils")] {
23 DEV_IDENTITY.version()
24 } else {
25 None
26 }
27 }
28}
29
30#[cfg(not(target_env = "sgx"))]
34pub(crate) fn measurement() -> Option<Measurement> {
35 cfg_if::cfg_if! {
36 if #[cfg(feature = "test-utils")] {
37 DEV_IDENTITY.measurement()
38 } else {
39 None
40 }
41 }
42}
43
44#[cfg(feature = "test-utils")]
46static DEV_IDENTITY: LazyLock<Identity> =
47 LazyLock::new(Identity::read_patch_slot);
48
49pub struct Identity([u8; Self::LEN]);
52
53impl Identity {
54 pub const LEN: usize = 64;
56
57 pub const PLACEHOLDER: Identity = Identity(
59 *b"\xffLEXE_DEV_ENCLAVE_IDENTITY_PATCH_SLOT_REPLACE_ME__0123456789ABCD",
60 );
61
62 pub const SECTION_ELF: &'static str = ".lexe_dev_id";
66
67 pub const SECTION_MACHO: &'static str = "__lexe_dev_id";
72
73 const MEASUREMENT_OFFSET: usize = 32;
74 const VERSION_MAX_LEN: usize = Self::MEASUREMENT_OFFSET - 1;
75 const VERSION_OFFSET: usize = 1;
76
77 pub fn encode(version: &str, measurement: Measurement) -> Self {
82 assert!(!version.is_empty(), "Dev version is empty");
83 let max_len = Self::VERSION_MAX_LEN;
84 assert!(
85 version.len() <= max_len,
86 "Dev version exceeds {max_len} bytes: {version}"
87 );
88
89 let mut encoded = [0; Self::LEN];
90 encoded[0] = version.len() as u8;
91 encoded[Self::VERSION_OFFSET..][..version.len()]
92 .copy_from_slice(version.as_bytes());
93 encoded[Self::MEASUREMENT_OFFSET..]
94 .copy_from_slice(measurement.as_slice());
95 Self(encoded)
96 }
97
98 pub fn as_bytes(&self) -> &[u8; Self::LEN] {
99 &self.0
100 }
101}
102
103#[cfg(any(test, feature = "test-utils"))]
106impl Identity {
107 const UNPATCHED_VERSION_LEN: u8 = 0xff;
108
109 fn version(&self) -> Option<&str> {
111 self.is_patched().then(|| {
112 let version_len = usize::from(self.0[0]);
113 assert!(
114 (1..=Self::VERSION_MAX_LEN).contains(&version_len),
115 "Invalid patched dev version length: {version_len}"
116 );
117 std::str::from_utf8(&self.0[Self::VERSION_OFFSET..][..version_len])
118 .expect("Patched dev version is not UTF-8")
119 })
120 }
121
122 #[cfg(any(test, not(target_env = "sgx")))]
124 fn measurement(&self) -> Option<Measurement> {
125 self.is_patched().then(|| {
126 let measurement: [u8; 32] =
127 self.0[Self::MEASUREMENT_OFFSET..].try_into().unwrap();
128 Measurement::new(measurement)
129 })
130 }
131
132 fn is_patched(&self) -> bool {
133 self.0[0] != Self::UNPATCHED_VERSION_LEN
134 }
135
136 #[cfg(feature = "test-utils")] #[inline(never)]
139 fn read_patch_slot() -> Self {
140 #[used]
141 #[cfg_attr(
144 not(target_vendor = "apple"),
145 unsafe(link_section = ".lexe_dev_id")
146 )]
147 #[cfg_attr(
149 target_vendor = "apple",
150 unsafe(link_section = "__DATA,__lexe_dev_id")
151 )]
152 static mut PATCH_SLOT: [u8; Identity::LEN] = Identity::PLACEHOLDER.0;
153
154 Self(unsafe { std::ptr::read(&raw mut PATCH_SLOT) })
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use super::*;
164
165 #[test]
166 fn encode_roundtrip() {
167 let version = "0.0.0-dev.1";
168 let measurement = Measurement::new([42; 32]);
169
170 let id = Identity::encode(version, measurement);
171
172 assert_eq!(id.version(), Some(version));
173 assert_eq!(id.measurement(), Some(measurement));
174 }
175
176 #[test]
177 fn unpatched_placeholder() {
178 assert!(Identity::PLACEHOLDER.version().is_none());
179 assert!(Identity::PLACEHOLDER.measurement().is_none());
180 }
181
182 #[cfg(feature = "test-utils")]
184 #[test]
185 fn unpatched_slot() {
186 assert!(version().is_none());
187 }
188}