1use core::ffi::{CStr, c_char};
18
19use portable_atomic::{AtomicPtr, AtomicUsize, Ordering};
20
21#[repr(C)]
25#[derive(Clone, Copy, Debug)]
26pub struct SessionProperty {
27 pub key: *const c_char,
29 pub value: *const c_char,
31}
32
33unsafe impl Sync for SessionProperty {}
37unsafe impl Send for SessionProperty {}
39
40impl SessionProperty {
41 pub const fn new(key: &'static CStr, value: &'static CStr) -> Self {
44 Self {
45 key: key.as_ptr(),
46 value: value.as_ptr(),
47 }
48 }
49
50 pub fn strs(&self) -> Option<(&'static str, &'static str)> {
53 let s = |p: *const c_char| -> Option<&'static str> {
54 if p.is_null() {
55 None
56 } else {
57 unsafe { CStr::from_ptr(p) }.to_str().ok()
60 }
61 };
62 Some((s(self.key)?, s(self.value)?))
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum BadSessionProperties {
69 Row(usize),
71 TooMany(usize),
73}
74
75impl core::fmt::Display for BadSessionProperties {
76 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77 match self {
78 Self::Row(i) => write!(
79 f,
80 "session property row {i} has a NULL key or value, or a string that is not UTF-8"
81 ),
82 Self::TooMany(n) => write!(
83 f,
84 "{n} session properties; one session open carries at most {MAX_SESSION_PROPERTIES}"
85 ),
86 }
87 }
88}
89
90pub const MAX_SESSION_PROPERTIES: usize = 16;
94
95static TABLE: AtomicPtr<SessionProperty> = AtomicPtr::new(core::ptr::null_mut());
96static LEN: AtomicUsize = AtomicUsize::new(0);
97
98pub fn set_session_properties(
103 table: &'static [SessionProperty],
104) -> Result<(), BadSessionProperties> {
105 if table.len() > MAX_SESSION_PROPERTIES {
106 return Err(BadSessionProperties::TooMany(table.len()));
107 }
108 if let Some(i) = table.iter().position(|r| r.strs().is_none()) {
109 return Err(BadSessionProperties::Row(i));
110 }
111 LEN.store(0, Ordering::Release);
115 TABLE.store(table.as_ptr() as *mut SessionProperty, Ordering::Release);
116 LEN.store(table.len(), Ordering::Release);
117 Ok(())
118}
119
120pub fn session_properties() -> &'static [SessionProperty] {
122 let len = LEN.load(Ordering::Acquire);
123 let ptr = TABLE.load(Ordering::Acquire);
124 if len == 0 || ptr.is_null() {
125 return &[];
126 }
127 unsafe { core::slice::from_raw_parts(ptr, len) }
130}
131
132pub fn with_session_properties<R>(
137 explicit: &[(&str, &str)],
138 f: impl FnOnce(&[(&str, &str)]) -> R,
139) -> R {
140 if !explicit.is_empty() {
141 return f(explicit);
142 }
143 let mut rows: heapless::Vec<(&'static str, &'static str), MAX_SESSION_PROPERTIES> =
144 heapless::Vec::new();
145 for r in session_properties() {
146 if let Some(kv) = r.strs() {
148 let _ = rows.push(kv);
149 }
150 }
151 f(&rows)
152}
153
154pub unsafe fn lent_session_properties<'a>(
169 rows: *const SessionProperty,
170 len: usize,
171 out: &mut [(&'a str, &'a str)],
172) -> Result<usize, BadSessionProperties> {
173 if len == 0 {
174 return Ok(0);
175 }
176 if rows.is_null() {
177 return Err(BadSessionProperties::Row(0));
178 }
179 if len > MAX_SESSION_PROPERTIES || len > out.len() {
180 return Err(BadSessionProperties::TooMany(len));
181 }
182 let rows = unsafe { core::slice::from_raw_parts(rows, len) };
184 for (i, (r, slot)) in rows.iter().zip(out.iter_mut()).enumerate() {
185 let s = |p: *const c_char| -> Option<&'a str> {
186 if p.is_null() {
187 None
188 } else {
189 unsafe { CStr::from_ptr(p) }.to_str().ok()
192 }
193 };
194 *slot = (
195 s(r.key).ok_or(BadSessionProperties::Row(i))?,
196 s(r.value).ok_or(BadSessionProperties::Row(i))?,
197 );
198 }
199 Ok(len)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::string::{String, ToString};
206
207 #[test]
208 fn a_lent_table_reads_into_the_callers_buffer_and_refuses_what_does_not_read() {
209 let rows = [
210 SessionProperty::new(c"mode", c"client"),
211 SessionProperty::new(c"connect", c"tcp/10.0.0.1:7447"),
212 ];
213 let mut out = [("", ""); 4];
214 let n = unsafe { lent_session_properties(rows.as_ptr(), rows.len(), &mut out) };
215 assert_eq!(n, Ok(2));
216 assert_eq!(
217 out[..2],
218 [("mode", "client"), ("connect", "tcp/10.0.0.1:7447")]
219 );
220
221 assert_eq!(
222 unsafe { lent_session_properties(core::ptr::null(), 0, &mut out) },
223 Ok(0),
224 "NULL with no rows is no table"
225 );
226 assert_eq!(
227 unsafe { lent_session_properties(core::ptr::null(), 1, &mut out) },
228 Err(BadSessionProperties::Row(0))
229 );
230 let bad = [
231 SessionProperty::new(c"mode", c"client"),
232 SessionProperty {
233 key: c"connect".as_ptr(),
234 value: core::ptr::null(),
235 },
236 ];
237 assert_eq!(
238 unsafe { lent_session_properties(bad.as_ptr(), bad.len(), &mut out) },
239 Err(BadSessionProperties::Row(1))
240 );
241 let mut one = [("", ""); 1];
242 assert_eq!(
243 unsafe { lent_session_properties(rows.as_ptr(), rows.len(), &mut one) },
244 Err(BadSessionProperties::TooMany(2)),
245 "more rows than the buffer holds is refused, never truncated"
246 );
247 let many = [SessionProperty::new(c"k", c"v"); MAX_SESSION_PROPERTIES + 1];
248 let mut big = [("", ""); MAX_SESSION_PROPERTIES + 1];
249 assert_eq!(
250 unsafe { lent_session_properties(many.as_ptr(), many.len(), &mut big) },
251 Err(BadSessionProperties::TooMany(MAX_SESSION_PROPERTIES + 1))
252 );
253 }
254
255 #[test]
256 fn a_table_installs_and_an_explicit_list_wins() {
257 static T: [SessionProperty; 2] = [
258 SessionProperty::new(c"mode", c"client"),
259 SessionProperty::new(c"connect", c"tcp/10.0.2.2:7447"),
260 ];
261 set_session_properties(&T).unwrap();
262 let got = with_session_properties(&[], |p| {
263 p.iter()
264 .map(|(k, v)| (k.to_string(), v.to_string()))
265 .collect::<std::vec::Vec<_>>()
266 });
267 let want: [(String, String); 2] = [
268 ("mode".into(), "client".into()),
269 ("connect".into(), "tcp/10.0.2.2:7447".into()),
270 ];
271 assert_eq!(got, want);
272 let explicit = [("listen", "tcp/0.0.0.0:1")];
273 assert_eq!(with_session_properties(&explicit, |p| p.len()), 1);
274
275 static BAD: [SessionProperty; 1] = [SessionProperty {
276 key: c"mode".as_ptr(),
277 value: core::ptr::null(),
278 }];
279 assert_eq!(
280 set_session_properties(&BAD),
281 Err(BadSessionProperties::Row(0))
282 );
283 assert_eq!(session_properties().len(), 2);
285 }
286}