Skip to main content

driven/format/
decoder.rs

1//! Binary decoder for .drv format
2
3use super::{
4    SectionType,
5    schema::{
6        ContextSection, DrvHeader, PersonaSection, RuleCategory, RuleEntry, StandardsSection,
7        WorkflowSection, WorkflowStep,
8    },
9};
10use crate::{DrivenError, Result, parser::UnifiedRule};
11use bytes::Buf;
12use std::io::Cursor;
13
14/// Decoder for reading .drv binary files
15#[derive(Debug)]
16pub struct DrvDecoder<'a> {
17    /// Raw data
18    data: &'a [u8],
19    /// Decoded string table
20    string_table: Vec<&'a str>,
21    /// Header
22    header: DrvHeader,
23}
24
25impl<'a> DrvDecoder<'a> {
26    /// Create a new decoder from binary data
27    pub fn new(data: &'a [u8]) -> Result<Self> {
28        if data.len() < 16 {
29            return Err(DrivenError::InvalidBinary(
30                "Data too short for header".to_string(),
31            ));
32        }
33
34        // Parse header
35        let header: DrvHeader = *bytemuck::from_bytes(&data[..16]);
36        header.validate()?;
37
38        // Verify checksum
39        let stored_checksum = header.checksum;
40        let computed = {
41            let hash = blake3::hash(&data[16..]);
42            let bytes = hash.as_bytes();
43            u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
44        };
45
46        if stored_checksum != computed {
47            return Err(DrivenError::InvalidBinary(format!(
48                "Checksum mismatch: stored={:08x}, computed={:08x}",
49                stored_checksum, computed
50            )));
51        }
52
53        let mut decoder = Self {
54            data,
55            string_table: Vec::new(),
56            header,
57        };
58
59        // Parse string table (always first section after header)
60        decoder.parse_string_table()?;
61
62        Ok(decoder)
63    }
64
65    /// Parse the string table section
66    fn parse_string_table(&mut self) -> Result<()> {
67        let mut cursor = Cursor::new(&self.data[16..]);
68
69        // Read section type
70        if cursor.remaining() < 1 {
71            return Err(DrivenError::InvalidBinary(
72                "Missing string table section".to_string(),
73            ));
74        }
75
76        let section_type = cursor.get_u8();
77        if section_type != SectionType::StringTable as u8 {
78            return Err(DrivenError::InvalidBinary(format!(
79                "Expected string table (0x01), got 0x{:02x}",
80                section_type
81            )));
82        }
83
84        // Read count
85        if cursor.remaining() < 4 {
86            return Err(DrivenError::InvalidBinary(
87                "Missing string table count".to_string(),
88            ));
89        }
90        let count = cursor.get_u32_le() as usize;
91
92        // Read strings
93        self.string_table.reserve(count);
94        let base_offset = 16 + 5; // header + section_type + count
95        let mut offset = base_offset;
96
97        for _ in 0..count {
98            if offset + 2 > self.data.len() {
99                return Err(DrivenError::InvalidBinary(
100                    "Unexpected end of string table".to_string(),
101                ));
102            }
103
104            let len = u16::from_le_bytes([self.data[offset], self.data[offset + 1]]) as usize;
105            offset += 2;
106
107            if offset + len > self.data.len() {
108                return Err(DrivenError::InvalidBinary(
109                    "String extends past end of data".to_string(),
110                ));
111            }
112
113            let s = std::str::from_utf8(&self.data[offset..offset + len])
114                .map_err(|e| DrivenError::InvalidBinary(format!("Invalid UTF-8: {}", e)))?;
115            self.string_table.push(s);
116            offset += len;
117        }
118
119        Ok(())
120    }
121
122    /// Get a string by index
123    pub fn get_string(&self, idx: u32) -> Result<&'a str> {
124        self.string_table
125            .get(idx as usize)
126            .copied()
127            .ok_or_else(|| DrivenError::InvalidBinary(format!("Invalid string index: {}", idx)))
128    }
129
130    /// Decode all rules from the binary
131    pub fn decode_all(&self) -> Result<Vec<UnifiedRule>> {
132        let mut rules = Vec::new();
133        let mut offset = 16 + 5; // Skip header and string table header
134
135        // Skip past string table data
136        for s in &self.string_table {
137            offset += 2 + s.len();
138        }
139
140        // Read remaining sections
141        while offset < self.data.len() {
142            let section_type = SectionType::try_from(self.data[offset])?;
143            offset += 1;
144
145            match section_type {
146                SectionType::StringTable => {
147                    // Already parsed
148                    unreachable!("String table should be first");
149                }
150                SectionType::Persona => {
151                    let (persona, new_offset) = self.decode_persona(offset)?;
152                    offset = new_offset;
153                    rules.push(UnifiedRule::Persona {
154                        name: self.get_string(persona.name_idx)?.to_string(),
155                        role: self.get_string(persona.role_idx)?.to_string(),
156                        identity: if persona.identity_idx > 0 {
157                            Some(self.get_string(persona.identity_idx)?.to_string())
158                        } else {
159                            None
160                        },
161                        style: if persona.style_idx > 0 {
162                            Some(self.get_string(persona.style_idx)?.to_string())
163                        } else {
164                            None
165                        },
166                        traits: persona
167                            .traits
168                            .iter()
169                            .map(|&idx| self.get_string(idx).map(String::from))
170                            .collect::<Result<Vec<_>>>()?,
171                        principles: persona
172                            .principles
173                            .iter()
174                            .map(|&idx| self.get_string(idx).map(String::from))
175                            .collect::<Result<Vec<_>>>()?,
176                    });
177                }
178                SectionType::Standards => {
179                    let (standards, new_offset) = self.decode_standards(offset)?;
180                    offset = new_offset;
181                    for rule in standards.rules {
182                        rules.push(UnifiedRule::Standard {
183                            category: rule.category,
184                            priority: rule.priority,
185                            description: self.get_string(rule.description_idx)?.to_string(),
186                            pattern: if rule.pattern_idx > 0 {
187                                Some(self.get_string(rule.pattern_idx)?.to_string())
188                            } else {
189                                None
190                            },
191                        });
192                    }
193                }
194                SectionType::Context => {
195                    let (context, new_offset) = self.decode_context(offset)?;
196                    offset = new_offset;
197                    rules.push(UnifiedRule::Context {
198                        includes: context
199                            .include_patterns
200                            .iter()
201                            .map(|&idx| self.get_string(idx).map(String::from))
202                            .collect::<Result<Vec<_>>>()?,
203                        excludes: context
204                            .exclude_patterns
205                            .iter()
206                            .map(|&idx| self.get_string(idx).map(String::from))
207                            .collect::<Result<Vec<_>>>()?,
208                        focus: context
209                            .focus_areas
210                            .iter()
211                            .map(|&idx| self.get_string(idx).map(String::from))
212                            .collect::<Result<Vec<_>>>()?,
213                    });
214                }
215                SectionType::Workflow => {
216                    let (workflow, new_offset) = self.decode_workflow(offset)?;
217                    offset = new_offset;
218                    let steps = workflow
219                        .steps
220                        .iter()
221                        .map(|step| {
222                            Ok(crate::parser::WorkflowStepData {
223                                name: self.get_string(step.name_idx)?.to_string(),
224                                description: self.get_string(step.description_idx)?.to_string(),
225                                condition: if step.condition_idx > 0 {
226                                    Some(self.get_string(step.condition_idx)?.to_string())
227                                } else {
228                                    None
229                                },
230                                actions: step
231                                    .actions
232                                    .iter()
233                                    .map(|&idx| self.get_string(idx).map(String::from))
234                                    .collect::<Result<Vec<_>>>()?,
235                            })
236                        })
237                        .collect::<Result<Vec<_>>>()?;
238                    rules.push(UnifiedRule::Workflow {
239                        name: self.get_string(workflow.name_idx)?.to_string(),
240                        steps,
241                    });
242                }
243            }
244        }
245
246        Ok(rules)
247    }
248
249    fn decode_persona(&self, offset: usize) -> Result<(PersonaSection, usize)> {
250        let mut cursor = Cursor::new(&self.data[offset..]);
251
252        let name_idx = cursor.get_u32_le();
253        let role_idx = cursor.get_u32_le();
254        let identity_idx = cursor.get_u32_le();
255        let style_idx = cursor.get_u32_le();
256
257        let traits_count = cursor.get_u16_le() as usize;
258        let mut traits = Vec::with_capacity(traits_count);
259        for _ in 0..traits_count {
260            traits.push(cursor.get_u32_le());
261        }
262
263        let principles_count = cursor.get_u16_le() as usize;
264        let mut principles = Vec::with_capacity(principles_count);
265        for _ in 0..principles_count {
266            principles.push(cursor.get_u32_le());
267        }
268
269        let persona = PersonaSection {
270            name_idx,
271            role_idx,
272            identity_idx,
273            style_idx,
274            traits,
275            principles,
276        };
277
278        Ok((persona, offset + cursor.position() as usize))
279    }
280
281    fn decode_standards(&self, offset: usize) -> Result<(StandardsSection, usize)> {
282        let mut cursor = Cursor::new(&self.data[offset..]);
283
284        let count = cursor.get_u32_le() as usize;
285        let mut rules = Vec::with_capacity(count);
286
287        for _ in 0..count {
288            let category = RuleCategory::from(cursor.get_u8());
289            let priority = cursor.get_u8();
290            let description_idx = cursor.get_u32_le();
291            let pattern_idx = cursor.get_u32_le();
292
293            rules.push(RuleEntry {
294                category,
295                priority,
296                description_idx,
297                pattern_idx,
298            });
299        }
300
301        Ok((
302            StandardsSection { rules },
303            offset + cursor.position() as usize,
304        ))
305    }
306
307    fn decode_context(&self, offset: usize) -> Result<(ContextSection, usize)> {
308        let mut cursor = Cursor::new(&self.data[offset..]);
309
310        let include_count = cursor.get_u16_le() as usize;
311        let mut include_patterns = Vec::with_capacity(include_count);
312        for _ in 0..include_count {
313            include_patterns.push(cursor.get_u32_le());
314        }
315
316        let exclude_count = cursor.get_u16_le() as usize;
317        let mut exclude_patterns = Vec::with_capacity(exclude_count);
318        for _ in 0..exclude_count {
319            exclude_patterns.push(cursor.get_u32_le());
320        }
321
322        let focus_count = cursor.get_u16_le() as usize;
323        let mut focus_areas = Vec::with_capacity(focus_count);
324        for _ in 0..focus_count {
325            focus_areas.push(cursor.get_u32_le());
326        }
327
328        let dep_count = cursor.get_u16_le() as usize;
329        let mut dependencies = Vec::with_capacity(dep_count);
330        for _ in 0..dep_count {
331            let name = cursor.get_u32_le();
332            let version = cursor.get_u32_le();
333            dependencies.push((name, version));
334        }
335
336        Ok((
337            ContextSection {
338                include_patterns,
339                exclude_patterns,
340                focus_areas,
341                dependencies,
342            },
343            offset + cursor.position() as usize,
344        ))
345    }
346
347    fn decode_workflow(&self, offset: usize) -> Result<(WorkflowSection, usize)> {
348        let mut cursor = Cursor::new(&self.data[offset..]);
349
350        let name_idx = cursor.get_u32_le();
351        let step_count = cursor.get_u16_le() as usize;
352
353        let mut steps = Vec::with_capacity(step_count);
354        for _ in 0..step_count {
355            let step_name_idx = cursor.get_u32_le();
356            let description_idx = cursor.get_u32_le();
357            let condition_idx = cursor.get_u32_le();
358
359            let action_count = cursor.get_u16_le() as usize;
360            let mut actions = Vec::with_capacity(action_count);
361            for _ in 0..action_count {
362                actions.push(cursor.get_u32_le());
363            }
364
365            steps.push(WorkflowStep {
366                name_idx: step_name_idx,
367                description_idx,
368                condition_idx,
369                actions,
370            });
371        }
372
373        Ok((
374            WorkflowSection { name_idx, steps },
375            offset + cursor.position() as usize,
376        ))
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::format::DrvEncoder;
384
385    #[test]
386    fn test_roundtrip_empty() {
387        let encoder = DrvEncoder::new();
388        let data = encoder.encode(&[]).unwrap();
389
390        let decoder = DrvDecoder::new(&data).unwrap();
391        let rules = decoder.decode_all().unwrap();
392
393        assert!(rules.is_empty());
394    }
395
396    #[test]
397    fn test_invalid_magic() {
398        let mut data = vec![0u8; 20];
399        data[0..4].copy_from_slice(b"BAD\0");
400
401        let result = DrvDecoder::new(&data);
402        assert!(result.is_err());
403    }
404
405    #[test]
406    fn test_too_short() {
407        let data = vec![0u8; 10];
408        let result = DrvDecoder::new(&data);
409        assert!(result.is_err());
410    }
411}