ghostscope_protocol/
streaming_parser.rs

1use crate::format_printer::FormatPrinter;
2use crate::trace_context::TraceContext;
3use crate::trace_event::*;
4use crate::TypeKind;
5use tracing::{debug, warn};
6use zerocopy::FromBytes;
7
8/// Event source type for parser buffer management
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum EventSource {
11    /// Continuous byte stream from RingBuf - may span multiple reads
12    /// Parser preserves residual bytes across events
13    #[default]
14    RingBuf,
15    /// Independent events from PerfEventArray - each event is complete
16    /// Parser clears buffer after each event to prevent pollution
17    PerfEventArray,
18}
19
20/// Parsed instruction from trace event
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22pub enum ParsedInstruction {
23    PrintString {
24        content: String,
25    },
26    PrintVariable {
27        name: String,
28        type_encoding: TypeKind,
29        formatted_value: String,
30        raw_data: Vec<u8>,
31    },
32    /// Structured runtime expression error/warning
33    ExprError {
34        expr: String,
35        error_code: u8,
36        flags: u8,
37        failing_addr: u64,
38    },
39    PrintComplexFormat {
40        formatted_output: String,
41    },
42    PrintComplexVariable {
43        name: String,
44        access_path: String,
45        type_index: u16,
46        formatted_value: String,
47        raw_data: Vec<u8>,
48    },
49    Backtrace {
50        depth: u8,
51    },
52    EndInstruction {
53        total_instructions: u16,
54        execution_status: u8,
55    },
56}
57
58/// Parsed trace event containing header, message, and instructions
59#[derive(Debug, Clone)]
60pub struct ParsedTraceEvent {
61    pub trace_id: u64,
62    pub timestamp: u64,
63    pub pid: u32,
64    pub tid: u32,
65    pub instructions: Vec<ParsedInstruction>,
66}
67
68impl ParsedTraceEvent {
69    /// Generate a formatted display output by combining format strings with variables
70    /// This handles the pattern: PrintString + PrintVariable sequence
71    pub fn to_formatted_output(&self) -> Vec<String> {
72        let mut output = Vec::new();
73        let mut i = 0;
74
75        while i < self.instructions.len() {
76            match &self.instructions[i] {
77                ParsedInstruction::PrintString { content } => {
78                    // Check if this looks like a format string (contains {})
79                    if content.contains("{}") {
80                        // Try to find corresponding variables
81                        let (formatted, consumed) =
82                            self.format_string_with_variables(content, i + 1);
83                        output.push(formatted);
84                        i += consumed; // Skip the variables we consumed
85                    } else {
86                        // Regular string, just add it
87                        output.push(content.clone());
88                        i += 1;
89                    }
90                }
91
92                ParsedInstruction::EndInstruction { .. } => {
93                    // Skip EndInstruction - it's for protocol control, not user output
94                    i += 1;
95                }
96                instruction => {
97                    // Other instructions (variables without format string, etc.)
98                    output.push(instruction.to_display_string());
99                    i += 1;
100                }
101            }
102        }
103
104        output
105    }
106
107    /// Format a format string with following variable instructions
108    fn format_string_with_variables(
109        &self,
110        format_string: &str,
111        start_index: usize,
112    ) -> (String, usize) {
113        // Count placeholders in format string
114        let placeholder_count = format_string.matches("{}").count();
115
116        // Collect variable values
117        let mut variables = Vec::new();
118        let mut consumed = 1; // At least consume the format string itself
119
120        for i in start_index..(start_index + placeholder_count).min(self.instructions.len()) {
121            if let ParsedInstruction::PrintVariable {
122                formatted_value, ..
123            } = &self.instructions[i]
124            {
125                variables.push(formatted_value.clone());
126                consumed += 1;
127            } else {
128                break; // Not a variable, stop collecting
129            }
130        }
131
132        // Apply formatting
133        let mut result = format_string.to_string();
134        for value in variables {
135            if let Some(pos) = result.find("{}") {
136                result.replace_range(pos..pos + 2, &value);
137            }
138        }
139
140        (result, consumed)
141    }
142}
143
144/// State of ongoing trace event parsing
145#[derive(Debug, Clone)]
146pub enum ParseState {
147    WaitingForHeader,
148    WaitingForMessage {
149        header: TraceEventHeader,
150    },
151    WaitingForInstructions {
152        header: TraceEventHeader,
153        message: TraceEventMessage,
154        instructions: Vec<ParsedInstruction>,
155    },
156    Complete,
157}
158
159/// Streaming parser for trace events received in segments
160/// TraceContext is externally managed by the loader
161pub struct StreamingTraceParser {
162    parse_state: ParseState,
163    buffer: Vec<u8>,
164    event_source: EventSource,
165}
166
167impl Default for StreamingTraceParser {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173impl StreamingTraceParser {
174    /// Create a new streaming parser with RingBuf mode (default)
175    /// Note: TraceContext is provided by loader during parsing
176    pub fn new() -> Self {
177        Self::with_event_source(EventSource::RingBuf)
178    }
179
180    /// Create a new streaming parser with specified event source
181    pub fn with_event_source(event_source: EventSource) -> Self {
182        Self {
183            parse_state: ParseState::WaitingForHeader,
184            buffer: Vec::with_capacity(1024),
185            event_source,
186        }
187    }
188
189    /// Process incoming data segment and return complete trace events
190    /// TraceContext is provided by the loader (uprobe config after compilation)
191    pub fn process_segment(
192        &mut self,
193        data: &[u8],
194        trace_context: &TraceContext,
195    ) -> Result<Option<ParsedTraceEvent>, String> {
196        // Append incoming data to buffer
197        self.buffer.extend_from_slice(data);
198
199        debug!(
200            "Processing segment of {} bytes, buffer now has {} bytes, state: {:?}",
201            data.len(),
202            self.buffer.len(),
203            self.parse_state
204        );
205
206        // Process buffer in a loop until we can't make progress
207        loop {
208            let consumed = match &self.parse_state {
209                ParseState::WaitingForHeader => {
210                    // Try to read header
211                    let (header, _rest) = match TraceEventHeader::read_from_prefix(&self.buffer) {
212                        Ok((h, r)) => (h, r),
213                        Err(_) => {
214                            debug!(
215                                "Waiting for more data for header (have {} bytes, need {})",
216                                self.buffer.len(),
217                                std::mem::size_of::<TraceEventHeader>()
218                            );
219                            return Ok(None);
220                        }
221                    };
222
223                    // Copy packed fields to avoid unaligned reference
224                    let magic = header.magic;
225                    if magic != crate::consts::MAGIC {
226                        return Err(format!("Invalid magic number: 0x{magic:x}"));
227                    }
228
229                    debug!("Received valid header: magic=0x{magic:x}");
230                    self.parse_state = ParseState::WaitingForMessage { header };
231                    std::mem::size_of::<TraceEventHeader>()
232                }
233
234                ParseState::WaitingForMessage { header } => {
235                    // Try to read message
236                    let (message, _rest) = match TraceEventMessage::read_from_prefix(&self.buffer) {
237                        Ok((m, r)) => (m, r),
238                        Err(_) => {
239                            debug!(
240                                "Waiting for more data for message (have {} bytes, need {})",
241                                self.buffer.len(),
242                                std::mem::size_of::<TraceEventMessage>()
243                            );
244                            return Ok(None);
245                        }
246                    };
247
248                    // Copy packed fields to avoid unaligned reference
249                    let trace_id = message.trace_id;
250                    let pid = message.pid;
251                    let tid = message.tid;
252                    debug!(
253                        "Received message: trace_id={}, pid={}, tid={}",
254                        trace_id, pid, tid
255                    );
256
257                    self.parse_state = ParseState::WaitingForInstructions {
258                        header: *header,
259                        message,
260                        instructions: Vec::new(),
261                    };
262                    std::mem::size_of::<TraceEventMessage>()
263                }
264
265                ParseState::WaitingForInstructions {
266                    header,
267                    message,
268                    instructions,
269                } => {
270                    // Try to parse instruction from buffer
271                    match self.try_parse_instruction(&self.buffer, trace_context)? {
272                        Some((parsed_instruction, consumed_bytes)) => {
273                            let mut new_instructions = instructions.clone();
274
275                            // Check if this is EndInstruction
276                            if matches!(
277                                parsed_instruction,
278                                ParsedInstruction::EndInstruction { .. }
279                            ) {
280                                new_instructions.push(parsed_instruction);
281
282                                // Complete trace event
283                                let complete_event = ParsedTraceEvent {
284                                    trace_id: message.trace_id,
285                                    timestamp: message.timestamp,
286                                    pid: message.pid,
287                                    tid: message.tid,
288                                    instructions: new_instructions,
289                                };
290
291                                debug!(
292                                    "Completed trace event with {} instructions",
293                                    complete_event.instructions.len()
294                                );
295
296                                // Reset state for next event
297                                self.parse_state = ParseState::WaitingForHeader;
298
299                                // Handle buffer cleanup based on event source
300                                match self.event_source {
301                                    EventSource::RingBuf => {
302                                        // RingBuf: continuous stream, preserve residual bytes
303                                        self.buffer.drain(..consumed_bytes);
304                                        debug!(
305                                            "RingBuf mode: consumed {} bytes, {} bytes remain in buffer",
306                                            consumed_bytes,
307                                            self.buffer.len()
308                                        );
309                                    }
310                                    EventSource::PerfEventArray => {
311                                        // PerfEventArray: independent events, clear all to prevent pollution
312                                        let residual = self.buffer.len() - consumed_bytes;
313                                        if residual > 0 {
314                                            warn!(
315                                                "PerfEventArray mode: discarding {} residual bytes after event",
316                                                residual
317                                            );
318                                        }
319                                        self.buffer.clear();
320                                        debug!("PerfEventArray mode: cleared buffer after complete event");
321                                    }
322                                }
323
324                                return Ok(Some(complete_event));
325                            } else {
326                                // Add instruction and continue waiting
327                                new_instructions.push(parsed_instruction);
328
329                                self.parse_state = ParseState::WaitingForInstructions {
330                                    header: *header,
331                                    message: *message,
332                                    instructions: new_instructions,
333                                };
334                                consumed_bytes
335                            }
336                        }
337                        None => {
338                            debug!("Waiting for more data for instruction");
339                            return Ok(None);
340                        }
341                    }
342                }
343
344                ParseState::Complete => {
345                    warn!("Received data while in Complete state, resetting");
346                    self.parse_state = ParseState::WaitingForHeader;
347                    continue;
348                }
349            };
350
351            // Consume processed bytes from buffer
352            if consumed > 0 {
353                self.buffer.drain(..consumed);
354                debug!(
355                    "Consumed {} bytes, buffer now has {} bytes",
356                    consumed,
357                    self.buffer.len()
358                );
359            }
360        }
361    }
362
363    /// Try to parse a single instruction from buffer
364    /// Returns Some((instruction, consumed_bytes)) if successful, None if need more data
365    fn try_parse_instruction(
366        &self,
367        data: &[u8],
368        trace_context: &TraceContext,
369    ) -> Result<Option<(ParsedInstruction, usize)>, String> {
370        // Try to read instruction header
371        let (inst_header, _rest) = match InstructionHeader::read_from_prefix(data) {
372            Ok((h, r)) => (h, r),
373            Err(_) => return Ok(None),
374        };
375
376        let expected_total_size =
377            std::mem::size_of::<InstructionHeader>() + inst_header.data_length as usize;
378        if data.len() < expected_total_size {
379            debug!(
380                "Waiting for complete instruction: have {} bytes, need {} bytes",
381                data.len(),
382                expected_total_size
383            );
384            return Ok(None);
385        }
386
387        let inst_data = &data[std::mem::size_of::<InstructionHeader>()..expected_total_size];
388
389        let instruction = match inst_header.inst_type {
390            t if t == InstructionType::PrintStringIndex as u8 => {
391                let (data_struct, _) = PrintStringIndexData::read_from_prefix(inst_data)
392                    .map_err(|_| "Invalid PrintStringIndex data".to_string())?;
393
394                let string_index = data_struct.string_index;
395                let string_content = trace_context
396                    .get_string(string_index)
397                    .ok_or_else(|| format!("Invalid string index: {string_index}"))?;
398
399                ParsedInstruction::PrintString {
400                    content: string_content.to_string(),
401                }
402            }
403
404            t if t == InstructionType::PrintVariableIndex as u8 => {
405                let (data_struct, _) = PrintVariableIndexData::read_from_prefix(inst_data)
406                    .map_err(|_| "Invalid PrintVariableIndex data".to_string())?;
407
408                let var_name_index = data_struct.var_name_index;
409                let var_name = trace_context
410                    .get_variable_name(var_name_index)
411                    .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
412
413                let var_data_offset = std::mem::size_of::<PrintVariableIndexData>();
414                if inst_data.len() < var_data_offset + data_struct.data_len as usize {
415                    return Err("Invalid variable data length".to_string());
416                }
417
418                let var_data =
419                    &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
420
421                let type_encoding =
422                    TypeKind::from_u8(data_struct.type_encoding).unwrap_or(TypeKind::Unknown);
423
424                // Use FormatPrinter with type context for enhanced formatting
425                let type_index = data_struct.type_index; // Copy to avoid packed field alignment issues
426                tracing::debug!("streaming_parser - type_index = {}", type_index);
427                tracing::debug!(
428                    "streaming_parser - TraceContext has {} types",
429                    trace_context.types.len()
430                );
431
432                let formatted_value = match trace_context.get_type(type_index) {
433                    Some(type_info) => {
434                        tracing::debug!(
435                            "streaming_parser - Found type_info for index {}",
436                            type_index
437                        );
438                        // Use advanced formatting with full type information
439                        crate::format_printer::FormatPrinter::format_data_with_type_info(
440                            var_data, type_info,
441                        )
442                    }
443                    None => {
444                        tracing::debug!(
445                            "streaming_parser - No type_info found for index {}",
446                            type_index
447                        );
448                        // Type information missing - this indicates a serious compiler bug
449                        format!(
450                            "<COMPILER_ERROR: type_index {type_index} not found in TraceContext>"
451                        )
452                    }
453                };
454
455                ParsedInstruction::PrintVariable {
456                    name: var_name.to_string(),
457                    type_encoding,
458                    formatted_value,
459                    raw_data: var_data.to_vec(),
460                }
461            }
462
463            t if t == InstructionType::ExprError as u8 => {
464                let (data_struct, _) =
465                    crate::trace_event::ExprErrorData::read_from_prefix(inst_data)
466                        .map_err(|_| "Invalid ExprError data".to_string())?;
467                let si = data_struct.string_index;
468                let expr = match trace_context.get_string(si) {
469                    Some(s) => s.to_string(),
470                    None => format!("<INVALID_EXPR_INDEX_{si}>"),
471                };
472                ParsedInstruction::ExprError {
473                    expr,
474                    error_code: data_struct.error_code,
475                    flags: data_struct.flags,
476                    failing_addr: data_struct.failing_addr,
477                }
478            }
479
480            t if t == InstructionType::PrintComplexFormat as u8 => {
481                let (format_data, _) = PrintComplexFormatData::read_from_prefix(inst_data)
482                    .map_err(|_| "Invalid PrintComplexFormat data".to_string())?;
483
484                // Parse complex variable data
485                let mut complex_variables = Vec::new();
486                let mut data_offset = std::mem::size_of::<PrintComplexFormatData>();
487
488                for _ in 0..format_data.arg_count {
489                    if data_offset + 7 > inst_data.len() {
490                        return Err("Invalid PrintComplexFormat argument data".to_string());
491                    }
492
493                    // Read complex variable header: var_name_index, type_index, access_path_len, status
494                    let var_name_index =
495                        u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
496                    let type_index = u16::from_le_bytes([
497                        inst_data[data_offset + 2],
498                        inst_data[data_offset + 3],
499                    ]);
500                    let access_path_len = inst_data[data_offset + 4] as usize;
501                    let status = inst_data[data_offset + 5];
502                    data_offset += 6; // 2+2+1(status)+1(ap_len)
503
504                    // Read access path
505                    if data_offset + access_path_len > inst_data.len() {
506                        return Err("Invalid PrintComplexFormat access path".to_string());
507                    }
508                    let access_path_bytes = &inst_data[data_offset..data_offset + access_path_len];
509                    let access_path = String::from_utf8_lossy(access_path_bytes).to_string();
510                    data_offset += access_path_len;
511
512                    // Read data length
513                    if data_offset + 2 > inst_data.len() {
514                        return Err("Invalid PrintComplexFormat data length".to_string());
515                    }
516                    let data_len =
517                        u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
518                    data_offset += 2;
519
520                    // Read variable data
521                    if data_offset + data_len as usize > inst_data.len() {
522                        return Err("Invalid PrintComplexFormat variable data".to_string());
523                    }
524                    let var_data = inst_data[data_offset..data_offset + data_len as usize].to_vec();
525                    data_offset += data_len as usize;
526
527                    complex_variables.push(crate::format_printer::ParsedComplexVariable {
528                        var_name_index,
529                        type_index,
530                        access_path,
531                        status,
532                        data: var_data,
533                    });
534                }
535
536                // Use FormatPrinter to generate formatted output
537                let formatted_output =
538                    crate::format_printer::FormatPrinter::format_complex_print_data(
539                        format_data.format_string_index,
540                        &complex_variables,
541                        trace_context,
542                    );
543
544                ParsedInstruction::PrintComplexFormat { formatted_output }
545            }
546
547            t if t == InstructionType::Backtrace as u8 => {
548                if inst_data.is_empty() {
549                    return Err("Invalid Backtrace data".to_string());
550                }
551
552                let depth = inst_data[0];
553                ParsedInstruction::Backtrace { depth }
554            }
555
556            t if t == InstructionType::PrintComplexVariable as u8 => {
557                let (data_struct, _) = PrintComplexVariableData::read_from_prefix(inst_data)
558                    .map_err(|_| "Invalid PrintComplexVariable data".to_string())?;
559
560                // Extract variable name
561                let var_name_index = data_struct.var_name_index;
562                let var_name = trace_context
563                    .get_variable_name(var_name_index)
564                    .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
565
566                // Extract access path
567                let access_path_len = data_struct.access_path_len as usize;
568                let struct_size = std::mem::size_of::<PrintComplexVariableData>();
569
570                if inst_data.len() < struct_size + access_path_len {
571                    return Err("Invalid PrintComplexVariable access path length".to_string());
572                }
573
574                let access_path_bytes = &inst_data[struct_size..struct_size + access_path_len];
575                let access_path = String::from_utf8_lossy(access_path_bytes);
576
577                // Extract variable data (either value or error payload)
578                let var_data_offset = struct_size + access_path_len;
579                if inst_data.len() < var_data_offset + data_struct.data_len as usize {
580                    return Err("Invalid PrintComplexVariable data length".to_string());
581                }
582
583                let var_data =
584                    &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
585
586                // Get type information and format with status-aware printer
587                let formatted_value = FormatPrinter::format_complex_variable_with_status(
588                    var_name_index,
589                    data_struct.type_index,
590                    &access_path,
591                    var_data,
592                    data_struct.status,
593                    trace_context,
594                );
595
596                ParsedInstruction::PrintComplexVariable {
597                    name: var_name.to_string(),
598                    access_path: access_path.to_string(),
599                    type_index: data_struct.type_index,
600                    formatted_value,
601                    raw_data: var_data.to_vec(),
602                }
603            }
604
605            t if t == InstructionType::EndInstruction as u8 => {
606                let (data_struct, _) = EndInstructionData::read_from_prefix(inst_data)
607                    .map_err(|_| "Invalid EndInstruction data".to_string())?;
608
609                ParsedInstruction::EndInstruction {
610                    total_instructions: data_struct.total_instructions,
611                    execution_status: data_struct.execution_status,
612                }
613            }
614
615            _ => {
616                return Err(format!(
617                    "Unknown instruction type: {}",
618                    inst_header.inst_type
619                ))
620            }
621        };
622
623        Ok(Some((instruction, expected_total_size)))
624    }
625
626    /// Reset parser state (useful for error recovery)
627    pub fn reset(&mut self) {
628        self.parse_state = ParseState::WaitingForHeader;
629        self.buffer.clear();
630    }
631
632    /// Get current parse state for debugging
633    pub fn get_state(&self) -> &ParseState {
634        &self.parse_state
635    }
636}
637
638impl ParsedInstruction {
639    /// Return a display string for this instruction
640    pub fn to_display_string(&self) -> String {
641        match self {
642            ParsedInstruction::PrintString { content } => {
643                format!("print \"{content}\"")
644            }
645            ParsedInstruction::PrintVariable {
646                name,
647                type_encoding,
648                formatted_value,
649                raw_data: _,
650            } => {
651                format!("{name} ({type_encoding:?}): {formatted_value}")
652            }
653            ParsedInstruction::ExprError {
654                expr,
655                error_code,
656                flags,
657                failing_addr,
658            } => {
659                // Map code to brief reason aligned with VariableStatus
660                // 1: NullDeref, 2: ReadError, 3: AccessError, 4: Truncated, 5: OffsetsUnavailable, 6: ZeroLength
661                let reason = match *error_code {
662                    1 => "null deref",
663                    2 => "read error",
664                    3 => "access error",
665                    4 => "truncated",
666                    5 => "offsets unavailable",
667                    6 => "zero length",
668                    _ => "error",
669                };
670
671                // Human-friendly flags (best-effort based on expr content)
672                fn readable_flags(expr: &str, flags: u8) -> Option<String> {
673                    if flags == 0 {
674                        return None;
675                    }
676                    let mut tags: Vec<&'static str> = Vec::new();
677                    let is_memcmp = expr.contains("memcmp(");
678                    let is_strncmp = expr.contains("strncmp(") || expr.contains("starts_with(");
679                    if is_memcmp {
680                        if (flags & 0x01) != 0 {
681                            tags.push("first-arg read-fail");
682                        }
683                        if (flags & 0x02) != 0 {
684                            tags.push("second-arg read-fail");
685                        }
686                        if (flags & 0x04) != 0 {
687                            tags.push("len-clamped");
688                        }
689                        if (flags & 0x08) != 0 {
690                            tags.push("len=0");
691                        }
692                    } else if is_strncmp {
693                        if (flags & 0x01) != 0 {
694                            tags.push("read-fail");
695                        }
696                        if (flags & 0x04) != 0 {
697                            tags.push("len-clamped");
698                        }
699                        if (flags & 0x08) != 0 {
700                            tags.push("len=0");
701                        }
702                    } else {
703                        // Unknown producer; fall back to hex for transparency
704                        return Some(format!("0x{flags:02x}"));
705                    }
706                    if tags.is_empty() {
707                        None
708                    } else {
709                        Some(tags.join(","))
710                    }
711                }
712
713                let flags_text = readable_flags(expr, *flags);
714                let addr_text = if *failing_addr != 0 {
715                    format!("at 0x{failing_addr:016x}")
716                } else {
717                    "at NULL".to_string()
718                };
719                let base = format!("ExprError: {expr} ({reason} {addr_text}");
720                match flags_text {
721                    Some(f) => format!("{base}, flags: {f})"),
722                    None => format!("{base})"),
723                }
724            }
725
726            ParsedInstruction::PrintComplexFormat { formatted_output } => formatted_output.clone(),
727            ParsedInstruction::PrintComplexVariable {
728                name: _,
729                access_path: _,
730                type_index: _,
731                formatted_value,
732                raw_data: _,
733            } => {
734                // formatted_value already contains "name = ..." or "name.access = ..."
735                formatted_value.clone()
736            }
737            ParsedInstruction::Backtrace { depth } => {
738                format!("backtrace({depth})")
739            }
740            ParsedInstruction::EndInstruction {
741                total_instructions,
742                execution_status,
743            } => {
744                let status_str = match *execution_status {
745                    0 => "success",
746                    1 => "partial_failure",
747                    2 => "complete_failure",
748                    _ => "unknown",
749                };
750                format!("end({total_instructions} instructions, {status_str})")
751            }
752        }
753    }
754
755    /// Return the instruction type as a string
756    pub fn instruction_type(&self) -> String {
757        match self {
758            ParsedInstruction::PrintString { .. } => "PrintString".to_string(),
759            ParsedInstruction::PrintVariable { .. } => "PrintVariable".to_string(),
760            ParsedInstruction::ExprError { .. } => "ExprError".to_string(),
761
762            ParsedInstruction::PrintComplexFormat { .. } => "PrintComplexFormat".to_string(),
763            ParsedInstruction::PrintComplexVariable { .. } => "PrintComplexVariable".to_string(),
764            ParsedInstruction::Backtrace { .. } => "Backtrace".to_string(),
765            ParsedInstruction::EndInstruction { .. } => "EndInstruction".to_string(),
766        }
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773
774    #[test]
775    fn test_streaming_parser() {
776        let mut trace_context = TraceContext::new();
777        let _str_idx = trace_context.add_string("hello world".to_string());
778
779        let mut parser = StreamingTraceParser::new();
780
781        // Create test segments
782        let header = TraceEventHeader {
783            magic: crate::consts::MAGIC,
784        };
785
786        let message = TraceEventMessage {
787            trace_id: 12345,
788            timestamp: 1000,
789            pid: 1001,
790            tid: 2002,
791        };
792
793        // Test header segment (using zerocopy to convert struct to bytes)
794        let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
795        let result = parser
796            .process_segment(header_bytes, &trace_context)
797            .unwrap();
798        assert!(result.is_none()); // Not complete yet
799
800        // Test message segment (using zerocopy to convert struct to bytes)
801        let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
802        let result = parser
803            .process_segment(message_bytes, &trace_context)
804            .unwrap();
805        assert!(result.is_none()); // Not complete yet
806
807        // TODO: Add instruction segments and EndInstruction test
808        // This demonstrates the pattern: TraceContext is managed externally by loader,
809        // not by the parser itself
810    }
811
812    #[test]
813    fn test_parse_exprerror_instruction() {
814        let mut trace_context = TraceContext::new();
815        let expr_idx = trace_context.add_string("memcmp(buf, hex(\"504f\"), 2)".to_string());
816
817        let mut parser = StreamingTraceParser::new();
818
819        // Header
820        let header = TraceEventHeader {
821            magic: crate::consts::MAGIC,
822        };
823        let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
824        assert!(parser
825            .process_segment(header_bytes, &trace_context)
826            .unwrap()
827            .is_none());
828
829        // Message
830        let message = TraceEventMessage {
831            trace_id: 1,
832            timestamp: 0,
833            pid: 123,
834            tid: 456,
835        };
836        let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
837        assert!(parser
838            .process_segment(message_bytes, &trace_context)
839            .unwrap()
840            .is_none());
841
842        // ExprError instruction: header(4) + payload(12)
843        let mut inst = Vec::new();
844        // InstructionHeader
845        inst.push(InstructionType::ExprError as u8); // inst_type
846        inst.extend_from_slice(
847            &(std::mem::size_of::<crate::trace_event::ExprErrorData>() as u16).to_le_bytes(),
848        ); // data_length
849        inst.push(0u8); // reserved
850                        // ExprErrorData payload
851        inst.extend_from_slice(&expr_idx.to_le_bytes()); // string_index
852        inst.push(1u8); // error_code
853        inst.push(0u8); // flags
854        inst.extend_from_slice(&0x1234_5678_9abc_def0u64.to_le_bytes()); // failing_addr
855
856        // EndInstruction
857        inst.push(InstructionType::EndInstruction as u8);
858        inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
859        inst.push(0u8); // reserved
860                        // EndInstructionData
861                        // EndInstructionData: total_instructions:u16, execution_status:u8, reserved:u8
862        inst.extend_from_slice(&1u16.to_le_bytes()); // total_instructions
863        inst.push(1u8); // execution_status
864        inst.push(0u8); // reserved
865
866        let event = parser
867            .process_segment(&inst, &trace_context)
868            .unwrap()
869            .expect("complete event");
870        assert_eq!(event.trace_id, 1);
871        assert_eq!(event.pid, 123);
872        assert_eq!(event.tid, 456);
873        assert_eq!(event.instructions.len(), 2);
874        match &event.instructions[0] {
875            ParsedInstruction::ExprError {
876                expr,
877                error_code,
878                flags,
879                failing_addr,
880            } => {
881                assert_eq!(expr, "memcmp(buf, hex(\"504f\"), 2)");
882                assert_eq!(*error_code, 1);
883                assert_eq!(*flags, 0);
884                assert_eq!(*failing_addr, 0x1234_5678_9abc_def0u64);
885            }
886            other => panic!("unexpected first instruction: {other:?}"),
887        }
888        match &event.instructions[1] {
889            ParsedInstruction::EndInstruction {
890                total_instructions,
891                execution_status,
892            } => {
893                assert_eq!(*total_instructions, 1);
894                assert_eq!(*execution_status, 1); // partial_failure
895            }
896            other => panic!("unexpected last instruction: {other:?}"),
897        }
898    }
899}