Skip to main content

dcp/binary/
invocation.rs

1//! Tool invocation structures for fixed-width binary argument passing.
2
3use crate::DCPError;
4
5/// Zero-copy tool invocation
6#[repr(C)]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ToolInvocation {
9    /// Pre-resolved tool ID (compile-time lookup)
10    pub tool_id: u32,
11    /// Bitfield describing argument positions and types
12    pub arg_layout: u64,
13    /// Offset into shared memory for arguments
14    pub args_offset: u32,
15    /// Length of arguments in shared memory
16    pub args_len: u32,
17}
18
19/// Argument types encoded in arg_layout
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u8)]
22pub enum ArgType {
23    Null = 0,
24    Bool = 1,
25    I32 = 2,
26    I64 = 3,
27    F64 = 4,
28    String = 5,
29    Bytes = 6,
30    Array = 7,
31    Object = 8,
32}
33
34impl ArgType {
35    /// Convert from u8 to ArgType
36    pub fn from_u8(value: u8) -> Option<Self> {
37        match value {
38            0 => Some(Self::Null),
39            1 => Some(Self::Bool),
40            2 => Some(Self::I32),
41            3 => Some(Self::I64),
42            4 => Some(Self::F64),
43            5 => Some(Self::String),
44            6 => Some(Self::Bytes),
45            7 => Some(Self::Array),
46            8 => Some(Self::Object),
47            _ => None,
48        }
49    }
50}
51
52impl ToolInvocation {
53    /// Size of the struct in bytes
54    pub const SIZE: usize = 24; // 4 + 8 + 4 + 4 + padding
55
56    /// Maximum number of arguments that can be encoded in arg_layout
57    /// Each argument uses 4 bits for type, so 64 bits / 4 = 16 args
58    pub const MAX_ARGS: usize = 16;
59
60    /// Create a new tool invocation
61    pub fn new(tool_id: u32, arg_layout: u64, args_offset: u32, args_len: u32) -> Self {
62        Self {
63            tool_id,
64            arg_layout,
65            args_offset,
66            args_len,
67        }
68    }
69
70    /// Parse from bytes.
71    #[inline(always)]
72    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DCPError> {
73        let bytes = bytes.as_ref();
74        if bytes.len() < Self::SIZE {
75            return Err(DCPError::InsufficientData);
76        }
77        if bytes.len() != Self::SIZE {
78            return Err(DCPError::ValidationFailed);
79        }
80        if bytes[4..8].iter().any(|&byte| byte != 0) {
81            return Err(DCPError::ValidationFailed);
82        }
83
84        Ok(Self {
85            tool_id: u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
86            arg_layout: u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
87            args_offset: u32::from_le_bytes(bytes[16..20].try_into().unwrap()),
88            args_len: u32::from_le_bytes(bytes[20..24].try_into().unwrap()),
89        })
90    }
91
92    /// Serialize to canonical bytes with reserved padding zeroed.
93    #[inline(always)]
94    pub fn as_bytes(&self) -> [u8; Self::SIZE] {
95        let mut bytes = [0u8; Self::SIZE];
96        bytes[0..4].copy_from_slice(&self.tool_id.to_le_bytes());
97        bytes[8..16].copy_from_slice(&self.arg_layout.to_le_bytes());
98        bytes[16..20].copy_from_slice(&self.args_offset.to_le_bytes());
99        bytes[20..24].copy_from_slice(&self.args_len.to_le_bytes());
100        bytes
101    }
102
103    /// Get the type of argument at the given index (0-15)
104    pub fn get_arg_type(&self, index: usize) -> Option<ArgType> {
105        if index >= Self::MAX_ARGS {
106            return None;
107        }
108        let shift = index * 4;
109        let type_bits = ((self.arg_layout >> shift) & 0xF) as u8;
110        ArgType::from_u8(type_bits)
111    }
112
113    /// Set the type of argument at the given index (0-15)
114    pub fn set_arg_type(&mut self, index: usize, arg_type: ArgType) {
115        if index >= Self::MAX_ARGS {
116            return;
117        }
118        let shift = index * 4;
119        // Clear the 4 bits at this position
120        self.arg_layout &= !(0xF << shift);
121        // Set the new type
122        self.arg_layout |= (arg_type as u64) << shift;
123    }
124
125    /// Count the number of non-null arguments
126    pub fn arg_count(&self) -> usize {
127        let mut count = 0;
128        for i in 0..Self::MAX_ARGS {
129            if let Some(arg_type) = self.get_arg_type(i) {
130                if arg_type != ArgType::Null {
131                    count += 1;
132                } else {
133                    break; // Null terminates the argument list
134                }
135            }
136        }
137        count
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_invocation_size() {
147        assert_eq!(std::mem::size_of::<ToolInvocation>(), ToolInvocation::SIZE);
148    }
149
150    #[test]
151    fn test_invocation_round_trip() {
152        let inv = ToolInvocation::new(42, 0x12345678, 100, 200);
153        let bytes = inv.as_bytes();
154        let parsed = ToolInvocation::from_bytes(bytes).unwrap();
155
156        assert_eq!(parsed.tool_id, 42);
157        assert_eq!(parsed.arg_layout, 0x12345678);
158        assert_eq!(parsed.args_offset, 100);
159        assert_eq!(parsed.args_len, 200);
160    }
161
162    #[test]
163    fn test_arg_type_encoding() {
164        let mut inv = ToolInvocation::new(1, 0, 0, 0);
165
166        inv.set_arg_type(0, ArgType::String);
167        inv.set_arg_type(1, ArgType::I32);
168        inv.set_arg_type(2, ArgType::Bool);
169
170        assert_eq!(inv.get_arg_type(0), Some(ArgType::String));
171        assert_eq!(inv.get_arg_type(1), Some(ArgType::I32));
172        assert_eq!(inv.get_arg_type(2), Some(ArgType::Bool));
173        assert_eq!(inv.get_arg_type(3), Some(ArgType::Null));
174    }
175
176    #[test]
177    fn test_arg_count() {
178        let mut inv = ToolInvocation::new(1, 0, 0, 0);
179        assert_eq!(inv.arg_count(), 0);
180
181        inv.set_arg_type(0, ArgType::String);
182        inv.set_arg_type(1, ArgType::I32);
183        assert_eq!(inv.arg_count(), 2);
184    }
185}