entrouter_universal/
envelope.rs1use crate::{fingerprint_str, UniversalError};
15use base64::{
16 engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
17 Engine,
18};
19use serde::{Deserialize, Serialize};
20use std::time::{SystemTime, UNIX_EPOCH};
21
22#[cfg(feature = "compression")]
23use crate::compress::{compress, decompress};
24
25#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
27pub enum EnvelopeMode {
28 Standard,
30 UrlSafe,
32 Compressed,
34 Ttl,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Envelope {
51 pub d: String,
53 pub f: String,
55 pub m: EnvelopeMode,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub e: Option<u64>,
60 pub v: u8,
62}
63
64impl Envelope {
65 #[must_use]
69 pub fn wrap(input: &str) -> Self {
70 Self {
71 d: STANDARD.encode(input.as_bytes()),
72 f: fingerprint_str(input),
73 m: EnvelopeMode::Standard,
74 e: None,
75 v: 3,
76 }
77 }
78
79 #[must_use]
83 pub fn wrap_url_safe(input: &str) -> Self {
84 Self {
85 d: URL_SAFE_NO_PAD.encode(input.as_bytes()),
86 f: fingerprint_str(input),
87 m: EnvelopeMode::UrlSafe,
88 e: None,
89 v: 3,
90 }
91 }
92
93 #[cfg(feature = "compression")]
97 pub fn wrap_compressed(input: &str) -> Result<Self, UniversalError> {
98 let compressed = compress(input.as_bytes())?;
99 Ok(Self {
100 d: STANDARD.encode(&compressed),
101 f: fingerprint_str(input),
102 m: EnvelopeMode::Compressed,
103 e: None,
104 v: 3,
105 })
106 }
107
108 #[must_use]
111 pub fn wrap_with_ttl(input: &str, ttl_secs: u64) -> Self {
112 let now = SystemTime::now()
113 .duration_since(UNIX_EPOCH)
114 .unwrap_or_default()
115 .as_secs();
116 Self {
117 d: STANDARD.encode(input.as_bytes()),
118 f: fingerprint_str(input),
119 m: EnvelopeMode::Ttl,
120 e: Some(now + ttl_secs),
121 v: 3,
122 }
123 }
124
125 pub fn unwrap_verified(&self) -> Result<String, UniversalError> {
133 if let Some(expiry) = self.e {
135 let now = SystemTime::now()
136 .duration_since(UNIX_EPOCH)
137 .unwrap_or_default()
138 .as_secs();
139 if now >= expiry {
140 return Err(UniversalError::Expired {
141 expired_at: expiry,
142 now,
143 });
144 }
145 }
146
147 let bytes = match self.m {
149 EnvelopeMode::Standard | EnvelopeMode::Ttl => STANDARD
150 .decode(&self.d)
151 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
152 EnvelopeMode::UrlSafe => URL_SAFE_NO_PAD
153 .decode(&self.d)
154 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
155 #[cfg(feature = "compression")]
156 EnvelopeMode::Compressed => {
157 let compressed = STANDARD
158 .decode(&self.d)
159 .map_err(|e| UniversalError::DecodeError(e.to_string()))?;
160 decompress(&compressed)?
161 }
162 #[cfg(not(feature = "compression"))]
163 EnvelopeMode::Compressed => {
164 return Err(UniversalError::DecodeError(
165 "compression feature not enabled".to_string(),
166 ))
167 }
168 };
169
170 let decoded =
171 String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))?;
172
173 let actual_fp = fingerprint_str(&decoded);
175 if actual_fp != self.f {
176 return Err(UniversalError::IntegrityViolation {
177 expected: self.f.clone(),
178 actual: actual_fp,
179 });
180 }
181
182 Ok(decoded)
183 }
184
185 pub fn unwrap_raw(&self) -> Result<String, UniversalError> {
187 let bytes = match self.m {
188 EnvelopeMode::Standard | EnvelopeMode::Ttl => STANDARD
189 .decode(&self.d)
190 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
191 EnvelopeMode::UrlSafe => URL_SAFE_NO_PAD
192 .decode(&self.d)
193 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
194 #[cfg(feature = "compression")]
195 EnvelopeMode::Compressed => {
196 let compressed = STANDARD
197 .decode(&self.d)
198 .map_err(|e| UniversalError::DecodeError(e.to_string()))?;
199 decompress(&compressed)?
200 }
201 #[cfg(not(feature = "compression"))]
202 EnvelopeMode::Compressed => {
203 return Err(UniversalError::DecodeError(
204 "compression feature not enabled".to_string(),
205 ))
206 }
207 };
208 String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))
209 }
210
211 pub fn is_expired(&self) -> bool {
213 if let Some(expiry) = self.e {
214 let now = SystemTime::now()
215 .duration_since(UNIX_EPOCH)
216 .unwrap_or_default()
217 .as_secs();
218 return now >= expiry;
219 }
220 false
221 }
222
223 pub fn ttl_remaining(&self) -> Option<u64> {
225 let expiry = self.e?;
226 let now = SystemTime::now()
227 .duration_since(UNIX_EPOCH)
228 .unwrap_or_default()
229 .as_secs();
230 Some(expiry.saturating_sub(now))
231 }
232
233 pub fn is_intact(&self) -> bool {
237 self.unwrap_verified().is_ok()
238 }
239
240 pub fn fingerprint(&self) -> &str {
242 &self.f
243 }
244
245 pub fn mode(&self) -> EnvelopeMode {
247 self.m
248 }
249
250 pub fn to_json(&self) -> Result<String, UniversalError> {
252 serde_json::to_string(self).map_err(|e| UniversalError::SerializationError(e.to_string()))
253 }
254
255 pub fn from_json(s: &str) -> Result<Self, UniversalError> {
257 serde_json::from_str(s).map_err(|e| UniversalError::SerializationError(e.to_string()))
258 }
259}