lora_database/snapshot/
json.rs1use anyhow::{anyhow, Result};
18
19use lora_snapshot::{
20 Compression, EncryptionKey, PasswordKdfParams, SnapshotCredentials, SnapshotEncryption,
21 SnapshotOptions, SnapshotPassword,
22};
23
24const DEFAULT_SNAPSHOT_KEY_ID: &str = "default";
25
26pub fn snapshot_options_from_json(value: Option<serde_json::Value>) -> Result<SnapshotOptions> {
39 let Some(value) = value else {
40 return Ok(SnapshotOptions {
41 compression: Compression::None,
42 encryption: None,
43 });
44 };
45 if value.is_null() {
46 return Ok(SnapshotOptions {
47 compression: Compression::None,
48 encryption: None,
49 });
50 }
51
52 let compression = match value.get("compression") {
53 Some(value) => parse_snapshot_compression_json(value)?,
54 None => Compression::None,
55 };
56 let encryption = parse_snapshot_credentials_json(Some(value))?;
57
58 Ok(SnapshotOptions {
59 compression,
60 encryption,
61 })
62}
63
64pub fn snapshot_credentials_from_json(
69 value: Option<serde_json::Value>,
70) -> Result<Option<SnapshotCredentials>> {
71 let Some(value) = value else {
72 return Ok(None);
73 };
74 if value.is_null() {
75 return Ok(None);
76 }
77 parse_snapshot_credentials_json(Some(value))
78}
79
80fn parse_snapshot_compression_json(value: &serde_json::Value) -> Result<Compression> {
81 match value {
82 serde_json::Value::Null => Ok(Compression::None),
83 serde_json::Value::String(format) => snapshot_compression_from_parts(format, None),
84 serde_json::Value::Object(obj) => {
85 let format =
86 string_field(obj, &["format", "type"])?.unwrap_or_else(|| "none".to_string());
87 let level = u32_field(obj, &["level"])?;
88 snapshot_compression_from_parts(&format, level)
89 }
90 _ => Err(anyhow!(
91 "snapshot compression must be a string or object with a format field"
92 )),
93 }
94}
95
96fn snapshot_compression_from_parts(format: &str, level: Option<u32>) -> Result<Compression> {
97 match format {
98 "none" | "identity" | "uncompressed" => Ok(Compression::None),
99 "gzip" => {
100 let level = level.unwrap_or(1);
101 if level > 9 {
102 return Err(anyhow!(
103 "gzip snapshot compression level must be between 0 and 9"
104 ));
105 }
106 Ok(Compression::Gzip { level })
107 }
108 other => Err(anyhow!("unknown snapshot compression '{other}'")),
109 }
110}
111
112fn parse_snapshot_credentials_json(
113 value: Option<serde_json::Value>,
114) -> Result<Option<SnapshotCredentials>> {
115 let Some(value) = value else {
116 return Ok(None);
117 };
118 if value.is_null() {
119 return Ok(None);
120 }
121
122 let credential_value = if let Some(credentials) = value.get("credentials") {
123 credentials
124 } else if let Some(encryption) = value.get("encryption") {
125 encryption
126 } else if looks_like_snapshot_encryption(&value) {
127 &value
128 } else {
129 return Ok(None);
130 };
131
132 if credential_value.is_null() {
133 return Ok(None);
134 }
135
136 Ok(Some(parse_snapshot_encryption_json(credential_value)?))
137}
138
139fn looks_like_snapshot_encryption(value: &serde_json::Value) -> bool {
140 let Some(obj) = value.as_object() else {
141 return false;
142 };
143 obj.contains_key("password")
144 || obj.contains_key("key")
145 || obj.contains_key("keyBytes")
146 || obj.contains_key("key_bytes")
147}
148
149fn parse_snapshot_encryption_json(value: &serde_json::Value) -> Result<SnapshotEncryption> {
150 let serde_json::Value::Object(obj) = value else {
151 return Err(anyhow!("snapshot encryption must be an object"));
152 };
153
154 let kind = string_field(obj, &["type", "kind"])?.unwrap_or_else(|| {
155 if obj.contains_key("key") || obj.contains_key("keyBytes") || obj.contains_key("key_bytes")
156 {
157 "key".to_string()
158 } else {
159 "password".to_string()
160 }
161 });
162
163 match kind.as_str() {
164 "password" | "passphrase" => {
165 let key_id = string_field(obj, &["keyId", "key_id"])?
166 .unwrap_or_else(|| DEFAULT_SNAPSHOT_KEY_ID.to_string());
167 let password = required_string_field(obj, &["password"])?;
168 let params = parse_password_kdf_params(
169 obj.get("params")
170 .or_else(|| obj.get("kdfParams"))
171 .or_else(|| obj.get("kdf_params")),
172 )?;
173 Ok(SnapshotEncryption::Password(SnapshotPassword::with_params(
174 key_id, password, params,
175 )))
176 }
177 "key" | "raw_key" | "rawKey" => {
178 let key_id = string_field(obj, &["keyId", "key_id"])?
179 .unwrap_or_else(|| DEFAULT_SNAPSHOT_KEY_ID.to_string());
180 let key = required_key_field(obj, &["key", "keyBytes", "key_bytes"])?;
181 Ok(SnapshotEncryption::Key(EncryptionKey::new(key_id, key)))
182 }
183 other => Err(anyhow!("unknown snapshot encryption type '{other}'")),
184 }
185}
186
187fn parse_password_kdf_params(value: Option<&serde_json::Value>) -> Result<PasswordKdfParams> {
188 let Some(value) = value else {
189 return Ok(PasswordKdfParams::interactive());
190 };
191 if value.is_null() {
192 return Ok(PasswordKdfParams::interactive());
193 }
194 let serde_json::Value::Object(obj) = value else {
195 return Err(anyhow!("snapshot password params must be an object"));
196 };
197
198 let defaults = PasswordKdfParams::interactive();
199 let memory_cost_kib =
200 u32_field(obj, &["memoryCostKib", "memory_cost_kib"])?.unwrap_or(defaults.memory_cost_kib);
201 let time_cost = u32_field(obj, &["timeCost", "time_cost"])?.unwrap_or(defaults.time_cost);
202 let parallelism = u32_field(obj, &["parallelism"])?.unwrap_or(defaults.parallelism);
203
204 if memory_cost_kib == 0 || time_cost == 0 || parallelism == 0 {
205 return Err(anyhow!(
206 "snapshot password params memoryCostKib, timeCost, and parallelism must be greater than zero"
207 ));
208 }
209
210 Ok(PasswordKdfParams {
211 memory_cost_kib,
212 time_cost,
213 parallelism,
214 })
215}
216
217fn string_field(
218 obj: &serde_json::Map<String, serde_json::Value>,
219 names: &[&str],
220) -> Result<Option<String>> {
221 for name in names {
222 if let Some(value) = obj.get(*name) {
223 return match value {
224 serde_json::Value::Null => Ok(None),
225 serde_json::Value::String(value) => Ok(Some(value.clone())),
226 _ => Err(anyhow!("snapshot field '{name}' must be a string")),
227 };
228 }
229 }
230 Ok(None)
231}
232
233fn required_string_field(
234 obj: &serde_json::Map<String, serde_json::Value>,
235 names: &[&str],
236) -> Result<String> {
237 string_field(obj, names)?.ok_or_else(|| anyhow!("snapshot field '{}' is required", names[0]))
238}
239
240fn u32_field(
241 obj: &serde_json::Map<String, serde_json::Value>,
242 names: &[&str],
243) -> Result<Option<u32>> {
244 for name in names {
245 if let Some(value) = obj.get(*name) {
246 return match value {
247 serde_json::Value::Null => Ok(None),
248 serde_json::Value::Number(number) => {
249 let Some(value) = number.as_u64() else {
250 return Err(anyhow!(
251 "snapshot field '{name}' must be a non-negative integer"
252 ));
253 };
254 if value > u32::MAX as u64 {
255 return Err(anyhow!("snapshot field '{name}' is too large"));
256 }
257 Ok(Some(value as u32))
258 }
259 _ => Err(anyhow!("snapshot field '{name}' must be an integer")),
260 };
261 }
262 }
263 Ok(None)
264}
265
266fn required_key_field(
267 obj: &serde_json::Map<String, serde_json::Value>,
268 names: &[&str],
269) -> Result<[u8; 32]> {
270 for name in names {
271 if let Some(value) = obj.get(*name) {
272 return parse_key_bytes(value, name);
273 }
274 }
275 Err(anyhow!("snapshot field '{}' is required", names[0]))
276}
277
278fn parse_key_bytes(value: &serde_json::Value, field_name: &str) -> Result<[u8; 32]> {
279 let serde_json::Value::Array(values) = value else {
280 return Err(anyhow!(
281 "snapshot field '{field_name}' must be an array of 32 byte values"
282 ));
283 };
284 if values.len() != 32 {
285 return Err(anyhow!(
286 "snapshot field '{field_name}' must contain exactly 32 byte values"
287 ));
288 }
289
290 let mut out = [0u8; 32];
291 for (idx, value) in values.iter().enumerate() {
292 let Some(byte) = value.as_u64() else {
293 return Err(anyhow!(
294 "snapshot field '{field_name}' item {idx} must be a byte integer"
295 ));
296 };
297 if byte > u8::MAX as u64 {
298 return Err(anyhow!(
299 "snapshot field '{field_name}' item {idx} must be between 0 and 255"
300 ));
301 }
302 out[idx] = byte as u8;
303 }
304 Ok(out)
305}