1use std::collections::HashMap;
2use std::time::{SystemTime, UNIX_EPOCH};
3use rand::random;
4use sha2::{Digest, Sha256};
5use serde::{Deserialize, Serialize};
6use dce_util::mixed::{DceErr, DceResult};
7#[cfg(feature = "async")]
8use async_trait::async_trait;
9
10pub const DEFAULT_ID_NAME: &str = "dcesid";
11pub const DEFAULT_TTL_MINUTES: u16 = 60;
12
13#[derive(Clone)]
14pub struct Meta {
15 sid_name: &'static str,
16 ttl_minutes: u16,
17 create_stamp: u64,
18 sid: String,
19 touches: Option<bool>,
20 #[cfg(feature = "test")]
21 sid_pool: Vec<String>,
22}
23
24impl Meta {
25 pub fn sid_name(&self) -> &str {
26 self.sid_name
27 }
28
29 pub fn ttl_minutes(&self) -> u16 {
30 self.ttl_minutes
31 }
32
33 pub fn ttl_seconds(&self) -> u32 {
34 self.ttl_minutes as u32 * 60
35 }
36
37 pub fn create_stamp(&self) -> u64 {
38 self.create_stamp
39 }
40
41 pub fn sid(&self) -> &str {
42 self.sid.as_str()
43 }
44
45 pub fn config(&mut self, sid_name: Option<&'static str>, ttl_minutes: Option<u16>) {
46 if let Some(sid_name) = sid_name { self.sid_name = sid_name; }
47 if let Some(ttl_minutes) = ttl_minutes { self.ttl_minutes = ttl_minutes; }
48 }
49
50 pub fn renew(&mut self, sid: Option<String>) -> DceResult<()> {
51 self.touches = None;
52 Ok(if let Some(sid) = sid {
53 (self.ttl_minutes, self.create_stamp) = Self::parse_sid(sid.as_str())?;
54 self.sid = sid;
55 } else {
56 (self.sid, self.create_stamp) = Self::generate_id(self.ttl_minutes, #[cfg(feature = "test")] &mut self.sid_pool)?;
57 })
58 }
59
60 pub fn new(ttl_minutes: u16) -> DceResult<Self> {
61 #[cfg(feature = "test")] let mut sid_pool = vec![];
62 let (sid, create_stamp) = Self::generate_id(ttl_minutes, #[cfg(feature = "test")] &mut sid_pool)?;
63 Ok(Self {ttl_minutes, create_stamp, sid, sid_name: DEFAULT_ID_NAME, touches: None, #[cfg(feature = "test")] sid_pool })
64 }
65
66 pub fn new_with_sid(mut sid_pool: Vec<String>) -> DceResult<Self> {
67 assert!(! sid_pool.is_empty());
68 let sid = sid_pool.remove(0);
69 let (ttl_minutes, create_stamp) = Self::parse_sid(&sid)?;
70 Ok(Self {ttl_minutes, create_stamp, sid, sid_name: DEFAULT_ID_NAME, touches: None, #[cfg(feature = "test")] sid_pool })
71 }
72
73 fn parse_sid(sid: &str) -> DceResult<(u16, u64)> {
74 const MIN_SID_LEN: usize = 76;
75 if sid.len() < MIN_SID_LEN { return DceErr::closed0_wrap(format!(r#"invalid sid "{}", less then {} chars"#, sid, MIN_SID_LEN)); }
76 let ttl_minutes = u16::from_str_radix(&sid[64..68], 16).map_err(DceErr::closed0)?;
77 let create_stamp = u64::from_str_radix(&sid[68..], 16).map_err(DceErr::closed0)?;
78 Ok((ttl_minutes, create_stamp))
79 }
80
81 #[allow(unused)]
82 fn generate_id(ttl_minutes: u16, #[cfg(feature = "test")] sid_pool: &mut Vec<String>) -> DceResult<(String, u64)> {
83 #[cfg(not(feature = "test"))] return Self::gen_id(ttl_minutes);
84 #[cfg(feature = "test")] return {
85 let sid = sid_pool.remove(0);
86 Self::parse_sid(&sid).map(|(_, create_stamp)| (sid, create_stamp))
87 };
88 }
89
90 pub fn gen_id(ttl_minutes: u16) -> DceResult<(String, u64)> {
91 let now = SystemTime::now().duration_since(UNIX_EPOCH).map_err(DceErr::closed0)?;
92 let now_secs = now.as_secs();
93 let mut hasher = Sha256::new();
94 hasher.update(format!("{}-{}", now.as_nanos(), random::<usize>()).as_bytes());
95 Ok((format!("{:X}{:04X}{:X}", hasher.finalize(), ttl_minutes, now_secs), now_secs))
96 }
97}
98
99
100macro_rules! auto_async {
101 { $($(#[$($meta:meta),+])+, $($async: ident)?);+ } => {
102 #[cfg_attr(feature = "async", async_trait)]
103 pub trait Session: Sized {
104 fn new(ttl_minutes: u16) -> DceResult<Self>;
105
106 fn new_with_id(sid_pool: Vec<String>) -> DceResult<Self>;
107
108 fn meta(&self) -> &Meta;
109
110 fn meta_mut(&mut self) -> &mut Meta;
111
112 fn id(&self) -> &str {
113 self.meta().sid()
114 }
115
116 fn key(&self) -> String {
117 Self::gen_key(self.meta().sid_name, self.id())
118 }
119
120 fn gen_key(sid_prefix: &str, id: &str) -> String;
121
122 $($(#[$($meta),+])+
123 $($async)? fn silent_set(&mut self, field: &str, value: &str) -> DceResult<bool>; )+
124
125 $($(#[$($meta),+])+
126 $($async)? fn silent_get(&mut self, field: &str) -> DceResult<String>; )+
127
128 $($(#[$($meta),+])+
129 $($async)? fn silent_del(&mut self, field: &str) -> DceResult<bool>; )+
130
131 $($(#[$($meta),+])+
132 $($async)? fn destroy(&mut self) -> DceResult<bool>; )+
133
134 $($(#[$($meta),+])+
135 $($async)? fn touch(&mut self) -> DceResult<bool>; )+
136
137 $($(#[$($meta),+])+
138 $($async)? fn load(&mut self, data: HashMap<String, String>) -> DceResult<bool>; )+
139
140 $($(#[$($meta),+])+
141 $($async)? fn raw(&mut self) -> DceResult<HashMap<String, String>>; )+
142
143 $($(#[$($meta),+])+
144 $($async)? fn ttl_passed(&mut self) -> DceResult<u32>; )+
145
146 $($(#[$($meta),+])+
147 $($async)? fn set<T: Serialize + Sync>(&mut self, field: &str, value: &T) -> DceResult<bool> {
148 let value = serde_json::to_string::<T>(value).map_err(DceErr::closed0)?;
149 #[cfg(feature = "async")]
150 match self.silent_set(field, &value).await {
151 Ok(res) if res => self.try_touch().await,
152 result => result,
153 }
154 #[cfg(not(feature = "async"))]
155 match self.silent_set(field, &value) {
156 Ok(res) if res => self.try_touch(),
157 result => result,
158 }
159 } )+
160
161 $($(#[$($meta),+])+
162 $($async)? fn get<T: for<'a> Deserialize<'a> + Send>(&mut self, field: &str) -> DceResult<T> {
163 #[cfg(feature = "async")]
164 let value = self.silent_get(field).await;
165 #[cfg(not(feature = "async"))]
166 let value = self.silent_get(field);
167 if value.is_ok() {
168 #[cfg(feature = "async")]
169 self.try_touch().await?;
170 #[cfg(not(feature = "async"))]
171 self.try_touch()?;
172 }
173 serde_json::from_str(&value?).map_err(DceErr::closed0)
174 } )+
175
176 $($(#[$($meta),+])+
177 $($async)? fn del(&mut self, field: &str) -> DceResult<bool> {
178 #[cfg(feature = "async")]
179 match self.silent_del(field).await {
180 Ok(res) if res => self.try_touch().await,
181 result => result,
182 }
183 #[cfg(not(feature = "async"))]
184 match self.silent_del(field) {
185 Ok(res) if res => self.try_touch(),
186 result => result,
187 }
188 } )+
189
190 $($(#[$($meta),+])+
191 $($async)? fn try_touch(&mut self) -> DceResult<bool> {
192 if self.meta().touches == None {
193 #[cfg(feature = "async")]
194 let touches = self.touch().await?;
195 #[cfg(not(feature = "async"))]
196 let touches = self.touch()?;
197 self.meta_mut().touches = Some(touches);
198 }
199 Ok(matches!(self.meta().touches, Some(true)))
200 } )+
201
202 $($(#[$($meta),+])+
203 $($async)? fn renew(&mut self, filters: HashMap<String, Option<String>>) -> DceResult<bool> where Self: Send {
204 #[cfg(feature = "async")]
205 return renew(self, filters).await;
206 #[cfg(not(feature = "async"))]
207 return renew(self, filters);
208 } )+
209
210 fn clone_with_id(&mut self, id: String) -> DceResult<Self> where Self: Clone {
211 clone_with_id(self, id)
212 }
213
214 fn cloned_mut(&mut self) -> &mut Option<Box<Self>>;
215
216 $($(#[$($meta),+])+
217 $($async)? fn cloned_silent_set(&mut self, field: &str, value: &str) -> DceResult<bool>; )+
218
219 $($(#[$($meta),+])+
220 $($async)? fn cloned_destroy(&mut self) -> DceResult<bool>; )+
221
222 $($(#[$($meta),+])+
223 $($async)? fn cloned_touch(&mut self) -> DceResult<bool>; )+
224
225 $($(#[$($meta),+])+
226 $($async)? fn cloned_ttl_passed(&mut self) -> DceResult<u32>; )+
227 }
228
229 pub fn clone_with_id<S: Session + Clone>(session: &mut S, id: String) -> DceResult<S> {
230 let mut cloned = session.clone();
231 cloned.meta_mut().renew(Some(id))?;
232 Ok(cloned)
233 }
234
235 $($(#[$($meta),+])+
236 pub $($async)? fn renew<S: Session + Send>(session: &mut S, filters: HashMap<String, Option<String>>) -> DceResult<bool> {
237 #[cfg(feature = "async")]
238 let mut raw = session.raw().await?;
239 #[cfg(not(feature = "async"))]
240 let mut raw = session.raw()?;
241 for (k, v) in filters {
242 if let Some(v) = v {
243 let _ = raw.insert(k, v);
244 } else {
245 let _ = raw.remove(&k);
246 }
247 }
248 session.meta_mut().renew(None)?;
249 if raw.is_empty() {
250 return Ok(true);
251 }
252 #[cfg(feature = "async")]
253 session.load(raw).await?;
254 #[cfg(not(feature = "async"))]
255 session.load(raw)?;
256 #[cfg(feature = "async")]
257 return session.try_touch().await;
258 #[cfg(not(feature = "async"))]
259 return session.try_touch();
260 } )+
261 };
262}
263
264auto_async!{ #[cfg(feature = "async")], async; #[cfg(not(feature = "async"))], }