Skip to main content

dcp/dispatch/
handler.rs

1//! Tool handler trait and types for zero-copy execution.
2
3use crate::protocol::ToolSchema;
4use crate::DCPError;
5
6/// Shared arguments for zero-copy access
7pub struct SharedArgs<'a> {
8    /// Raw bytes of arguments
9    data: &'a [u8],
10    /// Argument layout bitfield
11    layout: u64,
12}
13
14impl<'a> SharedArgs<'a> {
15    /// Create new shared args from bytes and layout
16    pub fn new(data: &'a [u8], layout: u64) -> Self {
17        Self { data, layout }
18    }
19
20    /// Get the raw data slice
21    pub fn data(&self) -> &[u8] {
22        self.data
23    }
24
25    /// Get the argument layout
26    pub fn layout(&self) -> u64 {
27        self.layout
28    }
29
30    /// Read a string at the given offset with bounds checking
31    pub fn read_str_at(&self, offset: usize, len: usize) -> Result<&str, DCPError> {
32        let end = offset.checked_add(len).ok_or(DCPError::OutOfBounds)?;
33        if end > self.data.len() {
34            return Err(DCPError::OutOfBounds);
35        }
36        std::str::from_utf8(&self.data[offset..end]).map_err(|_| DCPError::ValidationFailed)
37    }
38
39    /// Read an i32 at the given offset with bounds checking
40    pub fn read_i32_at(&self, offset: usize) -> Result<i32, DCPError> {
41        let end = offset.checked_add(4).ok_or(DCPError::OutOfBounds)?;
42        if end > self.data.len() {
43            return Err(DCPError::OutOfBounds);
44        }
45        let bytes: [u8; 4] = self.data[offset..end]
46            .try_into()
47            .map_err(|_| DCPError::OutOfBounds)?;
48        Ok(i32::from_le_bytes(bytes))
49    }
50
51    /// Read an i64 at the given offset with bounds checking
52    pub fn read_i64_at(&self, offset: usize) -> Result<i64, DCPError> {
53        let end = offset.checked_add(8).ok_or(DCPError::OutOfBounds)?;
54        if end > self.data.len() {
55            return Err(DCPError::OutOfBounds);
56        }
57        let bytes: [u8; 8] = self.data[offset..end]
58            .try_into()
59            .map_err(|_| DCPError::OutOfBounds)?;
60        Ok(i64::from_le_bytes(bytes))
61    }
62
63    /// Read a u32 at the given offset with bounds checking
64    pub fn read_u32_at(&self, offset: usize) -> Result<u32, DCPError> {
65        let end = offset.checked_add(4).ok_or(DCPError::OutOfBounds)?;
66        if end > self.data.len() {
67            return Err(DCPError::OutOfBounds);
68        }
69        let bytes: [u8; 4] = self.data[offset..end]
70            .try_into()
71            .map_err(|_| DCPError::OutOfBounds)?;
72        Ok(u32::from_le_bytes(bytes))
73    }
74
75    /// Read bytes at the given offset with bounds checking
76    pub fn read_bytes_at(&self, offset: usize, len: usize) -> Result<&[u8], DCPError> {
77        let end = offset.checked_add(len).ok_or(DCPError::OutOfBounds)?;
78        if end > self.data.len() {
79            return Err(DCPError::OutOfBounds);
80        }
81        Ok(&self.data[offset..end])
82    }
83
84    /// Read a bool at the given offset
85    pub fn read_bool_at(&self, offset: usize) -> Result<bool, DCPError> {
86        if offset >= self.data.len() {
87            return Err(DCPError::OutOfBounds);
88        }
89        Ok(self.data[offset] != 0)
90    }
91
92    /// Read an f64 at the given offset with bounds checking
93    pub fn read_f64_at(&self, offset: usize) -> Result<f64, DCPError> {
94        let end = offset.checked_add(8).ok_or(DCPError::OutOfBounds)?;
95        if end > self.data.len() {
96            return Err(DCPError::OutOfBounds);
97        }
98        let bytes: [u8; 8] = self.data[offset..end]
99            .try_into()
100            .map_err(|_| DCPError::OutOfBounds)?;
101        Ok(f64::from_le_bytes(bytes))
102    }
103}
104
105/// Result of tool execution
106#[derive(Debug, PartialEq)]
107pub enum ToolResult {
108    /// Success with binary payload
109    Success(Vec<u8>),
110    /// Success with no payload
111    Empty,
112    /// Error result
113    Error(DCPError),
114}
115
116impl ToolResult {
117    /// Create a success result with data
118    pub fn success(data: Vec<u8>) -> Self {
119        Self::Success(data)
120    }
121
122    /// Create an empty success result
123    pub fn empty() -> Self {
124        Self::Empty
125    }
126
127    /// Create an error result
128    pub fn error(err: DCPError) -> Self {
129        Self::Error(err)
130    }
131
132    /// Check if result is success
133    pub fn is_success(&self) -> bool {
134        matches!(self, Self::Success(_) | Self::Empty)
135    }
136
137    /// Get the payload if success
138    pub fn payload(&self) -> Option<&[u8]> {
139        match self {
140            Self::Success(data) => Some(data),
141            Self::Empty => Some(&[]),
142            Self::Error(_) => None,
143        }
144    }
145}
146
147/// Tool handler trait for executing tools
148pub trait ToolHandler: Send + Sync {
149    /// Execute the tool with zero-copy arguments
150    fn execute(&self, args: &SharedArgs) -> Result<ToolResult, DCPError>;
151
152    /// Get tool schema for validation
153    fn schema(&self) -> &ToolSchema;
154
155    /// Get the tool ID
156    fn tool_id(&self) -> u16 {
157        self.schema().id
158    }
159
160    /// Get the tool name
161    fn tool_name(&self) -> &'static str {
162        self.schema().name
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_shared_args_read_i32() {
172        let data = [0x01, 0x02, 0x03, 0x04];
173        let args = SharedArgs::new(&data, 0);
174        assert_eq!(args.read_i32_at(0).unwrap(), 0x04030201);
175    }
176
177    #[test]
178    fn test_shared_args_read_str() {
179        let data = b"hello world";
180        let args = SharedArgs::new(data, 0);
181        assert_eq!(args.read_str_at(0, 5).unwrap(), "hello");
182        assert_eq!(args.read_str_at(6, 5).unwrap(), "world");
183    }
184
185    #[test]
186    fn test_shared_args_bounds_check() {
187        let data = [0x01, 0x02];
188        let args = SharedArgs::new(&data, 0);
189        assert_eq!(args.read_i32_at(0), Err(DCPError::OutOfBounds));
190        assert_eq!(args.read_bytes_at(0, 10), Err(DCPError::OutOfBounds));
191    }
192
193    #[test]
194    fn test_tool_result() {
195        let success = ToolResult::success(vec![1, 2, 3]);
196        assert!(success.is_success());
197        assert_eq!(success.payload(), Some(&[1, 2, 3][..]));
198
199        let empty = ToolResult::empty();
200        assert!(empty.is_success());
201        assert_eq!(empty.payload(), Some(&[][..]));
202
203        let error = ToolResult::error(DCPError::ToolNotFound);
204        assert!(!error.is_success());
205        assert_eq!(error.payload(), None);
206    }
207}