saorsa_node/client/
quantum.rs1use super::data_types::{DataChunk, XorName};
20use crate::error::{Error, Result};
21use bytes::Bytes;
22use saorsa_core::P2PNode;
23use std::sync::Arc;
24use tracing::{debug, info};
25
26#[derive(Debug, Clone)]
28pub struct QuantumConfig {
29 pub timeout_secs: u64,
31 pub replica_count: u8,
33 pub encrypt_data: bool,
35}
36
37impl Default for QuantumConfig {
38 fn default() -> Self {
39 Self {
40 timeout_secs: 30,
41 replica_count: 4,
42 encrypt_data: true,
43 }
44 }
45}
46
47pub struct QuantumClient {
60 config: QuantumConfig,
61 p2p_node: Option<Arc<P2PNode>>,
62}
63
64impl QuantumClient {
65 #[must_use]
67 pub fn new(config: QuantumConfig) -> Self {
68 debug!("Creating quantum-resistant saorsa client");
69 Self {
70 config,
71 p2p_node: None,
72 }
73 }
74
75 #[must_use]
77 pub fn with_defaults() -> Self {
78 Self::new(QuantumConfig::default())
79 }
80
81 #[must_use]
83 pub fn with_node(mut self, node: Arc<P2PNode>) -> Self {
84 self.p2p_node = Some(node);
85 self
86 }
87
88 pub async fn get_chunk(&self, address: &XorName) -> Result<Option<DataChunk>> {
102 debug!(
103 "Querying saorsa network for chunk: {}",
104 hex::encode(address)
105 );
106
107 let Some(ref node) = self.p2p_node else {
108 return Err(Error::Network("P2P node not configured".into()));
109 };
110
111 let _ = self.config.timeout_secs; match node.dht_get(*address).await {
115 Ok(Some(data)) => {
116 debug!(
117 "Found chunk {} on saorsa network ({} bytes)",
118 hex::encode(address),
119 data.len()
120 );
121 Ok(Some(DataChunk::new(*address, Bytes::from(data))))
122 }
123 Ok(None) => {
124 debug!("Chunk {} not found on saorsa network", hex::encode(address));
125 Ok(None)
126 }
127 Err(e) => Err(Error::Network(format!(
128 "DHT lookup failed for {}: {}",
129 hex::encode(address),
130 e
131 ))),
132 }
133 }
134
135 pub async fn put_chunk(&self, content: Bytes) -> Result<XorName> {
152 use sha2::{Digest, Sha256};
153
154 debug!("Storing chunk on saorsa network ({} bytes)", content.len());
155
156 let Some(ref node) = self.p2p_node else {
157 return Err(Error::Network("P2P node not configured".into()));
158 };
159
160 let mut hasher = Sha256::new();
162 hasher.update(&content);
163 let hash = hasher.finalize();
164
165 let mut address = [0u8; 32];
166 address.copy_from_slice(&hash);
167
168 let _ = self.config.replica_count; node.dht_put(address, content.to_vec()).await.map_err(|e| {
172 Error::Network(format!(
173 "DHT store failed for {}: {}",
174 hex::encode(address),
175 e
176 ))
177 })?;
178
179 info!(
180 "Chunk stored at address: {} ({} bytes)",
181 hex::encode(address),
182 content.len()
183 );
184 Ok(address)
185 }
186
187 pub async fn exists(&self, address: &XorName) -> Result<bool> {
201 debug!(
202 "Checking existence on saorsa network: {}",
203 hex::encode(address)
204 );
205
206 let Some(ref node) = self.p2p_node else {
207 return Err(Error::Network("P2P node not configured".into()));
208 };
209
210 match node.dht_get(*address).await {
212 Ok(Some(_)) => {
213 debug!("Chunk {} exists on saorsa network", hex::encode(address));
214 Ok(true)
215 }
216 Ok(None) => {
217 debug!("Chunk {} not found on saorsa network", hex::encode(address));
218 Ok(false)
219 }
220 Err(e) => Err(Error::Network(format!(
221 "DHT lookup failed for {}: {}",
222 hex::encode(address),
223 e
224 ))),
225 }
226 }
227}
228
229#[cfg(test)]
230#[allow(clippy::unwrap_used, clippy::expect_used)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_quantum_config_default() {
236 let config = QuantumConfig::default();
237 assert_eq!(config.timeout_secs, 30);
238 assert_eq!(config.replica_count, 4);
239 assert!(config.encrypt_data);
240 }
241
242 #[test]
243 fn test_quantum_client_creation() {
244 let client = QuantumClient::with_defaults();
245 assert_eq!(client.config.timeout_secs, 30);
246 assert!(client.p2p_node.is_none());
247 }
248
249 #[tokio::test]
250 async fn test_get_chunk_without_node_fails() {
251 let client = QuantumClient::with_defaults();
252 let address = [0; 32];
253
254 let result = client.get_chunk(&address).await;
255 assert!(result.is_err());
256 }
257
258 #[tokio::test]
259 async fn test_put_chunk_without_node_fails() {
260 let client = QuantumClient::with_defaults();
261 let content = Bytes::from("test data");
262
263 let result = client.put_chunk(content).await;
264 assert!(result.is_err());
265 }
266
267 #[tokio::test]
268 async fn test_exists_without_node_fails() {
269 let client = QuantumClient::with_defaults();
270 let address = [0; 32];
271
272 let result = client.exists(&address).await;
273 assert!(result.is_err());
274 }
275}