/ abzu-dht / src / protocol.rs
protocol.rs
  1  //! Protocol messages for DHT operations
  2  //!
  3  //! These are the wire-level message types that will be serialized into AbzuFrame.
  4  //! Each message has a request and response variant.
  5  
  6  use serde::{Deserialize, Serialize};
  7  use crate::key::DhtKey;
  8  use crate::routing::NodeInfo;
  9  use crate::store::StoredValue;
 10  
 11  /// DHT protocol message — the top-level wrapper
 12  #[derive(Debug, Clone, Serialize, Deserialize)]
 13  pub enum DhtMessage {
 14      Request(Box<DhtRequest>),
 15      Response(DhtResponse),
 16  }
 17  
 18  /// Request types in the DHT protocol
 19  #[derive(Debug, Clone, Serialize, Deserialize)]
 20  pub enum DhtRequest {
 21      /// Ping: "Are you alive?"
 22      Ping {
 23          /// Sender's node info for routing table updates
 24          sender: NodeInfo,
 25      },
 26  
 27      /// FindNode: "What are the K closest nodes to this target?"
 28      FindNode {
 29          sender: NodeInfo,
 30          target: DhtKey,
 31      },
 32  
 33      /// FindValue: "Do you have values for this key? If not, give me closer nodes"
 34      FindValue {
 35          sender: NodeInfo,
 36          key: DhtKey,
 37      },
 38  
 39      /// Store: "Please store this value"
 40      Store {
 41          sender: NodeInfo,
 42          value: StoredValue,
 43      },
 44  
 45      /// GetProviders: "Who provides content with this CID?"
 46      /// (Higher-level convenience over FindValue with ContentProvider type)
 47      GetProviders {
 48          sender: NodeInfo,
 49          cid: DhtKey,
 50          /// Maximum providers to return
 51          max: usize,
 52      },
 53  
 54      /// AddProvider: "I'm providing content with this CID"
 55      AddProvider {
 56          sender: NodeInfo,
 57          cid: DhtKey,
 58          /// Provider announcement (signed)
 59          provider: StoredValue,
 60      },
 61  }
 62  
 63  /// Response types in the DHT protocol
 64  #[derive(Debug, Clone, Serialize, Deserialize)]
 65  pub enum DhtResponse {
 66      /// Response to Ping
 67      Pong {
 68          sender: NodeInfo,
 69      },
 70  
 71      /// Response to FindNode: K closest nodes
 72      Nodes {
 73          nodes: Vec<NodeInfo>,
 74      },
 75  
 76      /// Response to FindValue: either value(s) or closer nodes
 77      Value {
 78          /// Values found (may be empty)
 79          values: Vec<StoredValue>,
 80          /// Closer nodes if no values found
 81          closer_nodes: Vec<NodeInfo>,
 82      },
 83  
 84      /// Response to Store
 85      Stored {
 86          /// Whether the value was stored
 87          success: bool,
 88          /// Error message if not stored
 89          error: Option<String>,
 90      },
 91  
 92      /// Response to GetProviders
 93      Providers {
 94          providers: Vec<StoredValue>,
 95          closer_nodes: Vec<NodeInfo>,
 96      },
 97  
 98      /// Response to AddProvider
 99      ProviderAdded {
100          success: bool,
101          error: Option<String>,
102      },
103  
104      /// Error response (for any request)
105      Error {
106          code: ErrorCode,
107          message: String,
108      },
109  }
110  
111  /// Error codes for DHT operations
112  #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113  #[repr(u8)]
114  pub enum ErrorCode {
115      /// Unknown or unspecified error
116      Unknown = 0,
117      /// Request was malformed
118      BadRequest = 1,
119      /// Signature verification failed
120      InvalidSignature = 2,
121      /// Value has expired
122      Expired = 3,
123      /// Storage limit reached
124      StorageFull = 4,
125      /// Rate limited
126      RateLimited = 5,
127      /// Not authorized
128      Unauthorized = 6,
129      /// Internal error
130      Internal = 255,
131  }
132  
133  impl DhtRequest {
134      /// Get the sender's node info from any request
135      pub fn sender(&self) -> &NodeInfo {
136          match self {
137              Self::Ping { sender } => sender,
138              Self::FindNode { sender, .. } => sender,
139              Self::FindValue { sender, .. } => sender,
140              Self::Store { sender, .. } => sender,
141              Self::GetProviders { sender, .. } => sender,
142              Self::AddProvider { sender, .. } => sender,
143          }
144      }
145  
146      /// Check if this request should update the routing table
147      pub fn should_update_routing_table(&self) -> bool {
148          // All valid requests should update the routing table
149          true
150      }
151  }
152  
153  impl DhtResponse {
154      /// Create an error response
155      pub fn error(code: ErrorCode, message: impl Into<String>) -> Self {
156          Self::Error {
157              code,
158              message: message.into(),
159          }
160      }
161  }
162  
163  /// Builder for creating DHT messages
164  pub struct MessageBuilder {
165      our_node: NodeInfo,
166  }
167  
168  impl MessageBuilder {
169      /// Create a new builder with our node info
170      pub fn new(our_node: NodeInfo) -> Self {
171          Self { our_node }
172      }
173  
174      /// Create a Ping request
175      pub fn ping(&self) -> DhtMessage {
176          DhtMessage::Request(Box::new(DhtRequest::Ping {
177              sender: self.our_node.clone(),
178          }))
179      }
180  
181      /// Create a FindNode request
182      pub fn find_node(&self, target: DhtKey) -> DhtMessage {
183          DhtMessage::Request(Box::new(DhtRequest::FindNode {
184              sender: self.our_node.clone(),
185              target,
186          }))
187      }
188  
189      /// Create a FindValue request
190      pub fn find_value(&self, key: DhtKey) -> DhtMessage {
191          DhtMessage::Request(Box::new(DhtRequest::FindValue {
192              sender: self.our_node.clone(),
193              key,
194          }))
195      }
196  
197      /// Create a Store request
198      pub fn store(&self, value: StoredValue) -> DhtMessage {
199          DhtMessage::Request(Box::new(DhtRequest::Store {
200              sender: self.our_node.clone(),
201              value,
202          }))
203      }
204  
205      /// Create a GetProviders request
206      pub fn get_providers(&self, cid: DhtKey, max: usize) -> DhtMessage {
207          DhtMessage::Request(Box::new(DhtRequest::GetProviders {
208              sender: self.our_node.clone(),
209              cid,
210              max,
211          }))
212      }
213  
214      /// Create an AddProvider request
215      pub fn add_provider(&self, cid: DhtKey, provider: StoredValue) -> DhtMessage {
216          DhtMessage::Request(Box::new(DhtRequest::AddProvider {
217              sender: self.our_node.clone(),
218              cid,
219              provider,
220          }))
221      }
222  
223      /// Create a Pong response
224      pub fn pong(&self) -> DhtMessage {
225          DhtMessage::Response(DhtResponse::Pong {
226              sender: self.our_node.clone(),
227          })
228      }
229  
230      /// Create a Nodes response
231      pub fn nodes(&self, nodes: Vec<NodeInfo>) -> DhtMessage {
232          DhtMessage::Response(DhtResponse::Nodes { nodes })
233      }
234  
235      /// Create a Value response
236      pub fn value(&self, values: Vec<StoredValue>, closer_nodes: Vec<NodeInfo>) -> DhtMessage {
237          DhtMessage::Response(DhtResponse::Value {
238              values,
239              closer_nodes,
240          })
241      }
242  }
243  
244  #[cfg(test)]
245  mod tests {
246      use super::*;
247  
248      fn our_node() -> NodeInfo {
249          let pubkey = [0x42u8; 32];
250          NodeInfo::new(pubkey, "test:1234".to_string(), 1000)
251      }
252  
253      #[test]
254      fn test_message_builder() {
255          let builder = MessageBuilder::new(our_node());
256  
257          let ping = builder.ping();
258          assert!(matches!(ping, DhtMessage::Request(ref req) if matches!(**req, DhtRequest::Ping { .. })));
259  
260          let find = builder.find_node([0xABu8; 32]);
261          assert!(matches!(find, DhtMessage::Request(ref req) if matches!(**req, DhtRequest::FindNode { .. })));
262      }
263  
264      #[test]
265      fn test_sender_extraction() {
266          let node = our_node();
267          let req = DhtRequest::Ping { sender: node.clone() };
268          assert_eq!(req.sender().id, node.id);
269      }
270  }