Skip to main content

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        requested_depth: u8,
51        flags: u8,
52        status: BacktraceStatus,
53        error_code: u16,
54        frames: Vec<ParsedBacktraceFrame>,
55    },
56    EndInstruction {
57        total_instructions: u16,
58        execution_status: u8,
59    },
60}
61
62#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
63pub struct ParsedBacktraceFrame {
64    pub module_cookie: u64,
65    pub pc: u64,
66    pub raw_ip: u64,
67    pub flags: u16,
68}
69
70/// Parsed trace event containing header, message, and instructions
71#[derive(Debug, Clone)]
72pub struct ParsedTraceEvent {
73    pub trace_id: u64,
74    pub timestamp: u64,
75    pub pid: u32,
76    pub tid: u32,
77    pub instructions: Vec<ParsedInstruction>,
78}
79
80impl ParsedTraceEvent {
81    pub fn has_formatted_output(&self) -> bool {
82        self.instructions
83            .iter()
84            .any(|instruction| !matches!(instruction, ParsedInstruction::EndInstruction { .. }))
85    }
86
87    pub fn try_for_each_formatted_output<E>(
88        &self,
89        mut emit: impl FnMut(&str) -> Result<(), E>,
90    ) -> Result<(), E> {
91        let mut i = 0;
92
93        while i < self.instructions.len() {
94            match &self.instructions[i] {
95                ParsedInstruction::PrintString { content } => {
96                    if content.contains("{}") {
97                        let (formatted, consumed) =
98                            self.format_string_with_variables(content, i + 1);
99                        emit(&formatted)?;
100                        i += consumed;
101                    } else {
102                        emit(content)?;
103                        i += 1;
104                    }
105                }
106
107                ParsedInstruction::EndInstruction { .. } => {
108                    i += 1;
109                }
110                instruction => {
111                    let line = instruction.to_display_string();
112                    emit(&line)?;
113                    i += 1;
114                }
115            }
116        }
117
118        Ok(())
119    }
120
121    /// Generate a formatted display output by combining format strings with variables
122    /// This handles the pattern: PrintString + PrintVariable sequence
123    pub fn to_formatted_output(&self) -> Vec<String> {
124        let mut output = Vec::new();
125        let _ =
126            self.try_for_each_formatted_output(|line| -> Result<(), std::convert::Infallible> {
127                output.push(line.to_string());
128                Ok(())
129            });
130
131        output
132    }
133
134    /// Format a format string with following variable instructions
135    fn format_string_with_variables(
136        &self,
137        format_string: &str,
138        start_index: usize,
139    ) -> (String, usize) {
140        // Count placeholders in format string
141        let placeholder_count = format_string.matches("{}").count();
142
143        let mut consumed = 1; // At least consume the format string itself
144        let mut result = String::with_capacity(format_string.len());
145        let mut remaining = format_string;
146
147        for instruction_index in
148            start_index..(start_index + placeholder_count).min(self.instructions.len())
149        {
150            let Some(pos) = remaining.find("{}") else {
151                break;
152            };
153
154            if let Some(ParsedInstruction::PrintVariable {
155                formatted_value, ..
156            }) = self.instructions.get(instruction_index)
157            {
158                result.push_str(&remaining[..pos]);
159                result.push_str(formatted_value);
160                consumed += 1;
161                remaining = &remaining[pos + 2..];
162            } else {
163                break;
164            }
165        }
166        result.push_str(remaining);
167
168        (result, consumed)
169    }
170}
171
172/// State of ongoing trace event parsing
173#[derive(Debug, Clone)]
174pub enum ParseState {
175    WaitingForHeader,
176    WaitingForMessage {
177        header: TraceEventHeader,
178    },
179    WaitingForInstructions {
180        header: TraceEventHeader,
181        message: TraceEventMessage,
182        instructions: Vec<ParsedInstruction>,
183    },
184    Complete,
185}
186
187/// Streaming parser for trace events received in segments
188/// TraceContext is externally managed by the loader
189pub struct StreamingTraceParser {
190    parse_state: ParseState,
191    buffer: Vec<u8>,
192    event_source: EventSource,
193}
194
195impl Default for StreamingTraceParser {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201impl StreamingTraceParser {
202    /// Create a new streaming parser with RingBuf mode (default)
203    /// Note: TraceContext is provided by loader during parsing
204    pub fn new() -> Self {
205        Self::with_event_source(EventSource::RingBuf)
206    }
207
208    /// Create a new streaming parser with specified event source
209    pub fn with_event_source(event_source: EventSource) -> Self {
210        Self {
211            parse_state: ParseState::WaitingForHeader,
212            buffer: Vec::with_capacity(1024),
213            event_source,
214        }
215    }
216
217    /// Process incoming data segment and return complete trace events
218    /// TraceContext is provided by the loader (uprobe config after compilation)
219    pub fn process_segment(
220        &mut self,
221        data: &[u8],
222        trace_context: &TraceContext,
223    ) -> Result<Option<ParsedTraceEvent>, String> {
224        // Append incoming data to buffer
225        self.buffer.extend_from_slice(data);
226
227        debug!(
228            "Processing segment of {} bytes, buffer now has {} bytes, state: {:?}",
229            data.len(),
230            self.buffer.len(),
231            self.parse_state
232        );
233
234        // Process buffer in a loop until we can't make progress
235        loop {
236            let state = std::mem::replace(&mut self.parse_state, ParseState::Complete);
237            let consumed = match state {
238                ParseState::WaitingForHeader => {
239                    // Try to read header
240                    let (header, _rest) = match TraceEventHeader::read_from_prefix(&self.buffer) {
241                        Ok((h, r)) => (h, r),
242                        Err(_) => {
243                            self.parse_state = ParseState::WaitingForHeader;
244                            debug!(
245                                "Waiting for more data for header (have {} bytes, need {})",
246                                self.buffer.len(),
247                                std::mem::size_of::<TraceEventHeader>()
248                            );
249                            return Ok(None);
250                        }
251                    };
252
253                    // Copy packed fields to avoid unaligned reference
254                    let magic = header.magic;
255                    if magic != crate::consts::MAGIC {
256                        return Err(format!("Invalid magic number: 0x{magic:x}"));
257                    }
258
259                    debug!("Received valid header: magic=0x{magic:x}");
260                    self.parse_state = ParseState::WaitingForMessage { header };
261                    std::mem::size_of::<TraceEventHeader>()
262                }
263
264                ParseState::WaitingForMessage { header } => {
265                    // Try to read message
266                    let (message, _rest) = match TraceEventMessage::read_from_prefix(&self.buffer) {
267                        Ok((m, r)) => (m, r),
268                        Err(_) => {
269                            self.parse_state = ParseState::WaitingForMessage { header };
270                            debug!(
271                                "Waiting for more data for message (have {} bytes, need {})",
272                                self.buffer.len(),
273                                std::mem::size_of::<TraceEventMessage>()
274                            );
275                            return Ok(None);
276                        }
277                    };
278
279                    // Copy packed fields to avoid unaligned reference
280                    let trace_id = message.trace_id;
281                    let pid = message.pid;
282                    let tid = message.tid;
283                    debug!(
284                        "Received message: trace_id={}, pid={}, tid={}",
285                        trace_id, pid, tid
286                    );
287
288                    self.parse_state = ParseState::WaitingForInstructions {
289                        header,
290                        message,
291                        instructions: Vec::new(),
292                    };
293                    std::mem::size_of::<TraceEventMessage>()
294                }
295
296                ParseState::WaitingForInstructions {
297                    header,
298                    message,
299                    mut instructions,
300                } => {
301                    // Try to parse instruction from buffer
302                    match self.try_parse_instruction(&self.buffer, trace_context)? {
303                        Some((parsed_instruction, consumed_bytes)) => {
304                            // Check if this is EndInstruction
305                            if matches!(
306                                parsed_instruction,
307                                ParsedInstruction::EndInstruction { .. }
308                            ) {
309                                instructions.push(parsed_instruction);
310
311                                // Complete trace event
312                                let complete_event = ParsedTraceEvent {
313                                    trace_id: message.trace_id,
314                                    timestamp: message.timestamp,
315                                    pid: message.pid,
316                                    tid: message.tid,
317                                    instructions,
318                                };
319
320                                debug!(
321                                    "Completed trace event with {} instructions",
322                                    complete_event.instructions.len()
323                                );
324
325                                // Reset state for next event
326                                self.parse_state = ParseState::WaitingForHeader;
327
328                                // Handle buffer cleanup based on event source
329                                match self.event_source {
330                                    EventSource::RingBuf => {
331                                        // RingBuf: continuous stream, preserve residual bytes
332                                        self.buffer.drain(..consumed_bytes);
333                                        debug!(
334                                            "RingBuf mode: consumed {} bytes, {} bytes remain in buffer",
335                                            consumed_bytes,
336                                            self.buffer.len()
337                                        );
338                                    }
339                                    EventSource::PerfEventArray => {
340                                        // PerfEventArray sends each GhostScope event as one
341                                        // bpf_perf_event_output payload. Any bytes after our
342                                        // EndInstruction are perf record framing/alignment, not
343                                        // the start of another GhostScope event.
344                                        self.buffer.clear();
345                                        debug!("PerfEventArray mode: cleared buffer after complete event");
346                                    }
347                                }
348
349                                return Ok(Some(complete_event));
350                            } else {
351                                // Add instruction and continue waiting
352                                instructions.push(parsed_instruction);
353
354                                self.parse_state = ParseState::WaitingForInstructions {
355                                    header,
356                                    message,
357                                    instructions,
358                                };
359                                consumed_bytes
360                            }
361                        }
362                        None => {
363                            self.parse_state = ParseState::WaitingForInstructions {
364                                header,
365                                message,
366                                instructions,
367                            };
368                            debug!("Waiting for more data for instruction");
369                            return Ok(None);
370                        }
371                    }
372                }
373
374                ParseState::Complete => {
375                    warn!("Received data while in Complete state, resetting");
376                    self.parse_state = ParseState::WaitingForHeader;
377                    continue;
378                }
379            };
380
381            // Consume processed bytes from buffer
382            if consumed > 0 {
383                self.buffer.drain(..consumed);
384                debug!(
385                    "Consumed {} bytes, buffer now has {} bytes",
386                    consumed,
387                    self.buffer.len()
388                );
389            }
390        }
391    }
392
393    /// Try to parse a single instruction from buffer
394    /// Returns Some((instruction, consumed_bytes)) if successful, None if need more data
395    fn try_parse_instruction(
396        &self,
397        data: &[u8],
398        trace_context: &TraceContext,
399    ) -> Result<Option<(ParsedInstruction, usize)>, String> {
400        // Try to read instruction header
401        let (inst_header, _rest) = match InstructionHeader::read_from_prefix(data) {
402            Ok((h, r)) => (h, r),
403            Err(_) => return Ok(None),
404        };
405
406        let expected_total_size =
407            std::mem::size_of::<InstructionHeader>() + inst_header.data_length as usize;
408        if data.len() < expected_total_size {
409            debug!(
410                "Waiting for complete instruction: have {} bytes, need {} bytes",
411                data.len(),
412                expected_total_size
413            );
414            return Ok(None);
415        }
416
417        let inst_data = &data[std::mem::size_of::<InstructionHeader>()..expected_total_size];
418
419        let instruction = match inst_header.inst_type {
420            t if t == InstructionType::PrintStringIndex as u8 => {
421                let (data_struct, _) = PrintStringIndexData::read_from_prefix(inst_data)
422                    .map_err(|_| "Invalid PrintStringIndex data".to_string())?;
423
424                let string_index = data_struct.string_index;
425                let string_content = trace_context
426                    .get_string(string_index)
427                    .ok_or_else(|| format!("Invalid string index: {string_index}"))?;
428
429                ParsedInstruction::PrintString {
430                    content: string_content.to_string(),
431                }
432            }
433
434            t if t == InstructionType::PrintVariableIndex as u8 => {
435                let (data_struct, _) = PrintVariableIndexData::read_from_prefix(inst_data)
436                    .map_err(|_| "Invalid PrintVariableIndex data".to_string())?;
437
438                let var_name_index = data_struct.var_name_index;
439                let var_name = trace_context
440                    .get_variable_name(var_name_index)
441                    .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
442
443                let var_data_offset = std::mem::size_of::<PrintVariableIndexData>();
444                if inst_data.len() < var_data_offset + data_struct.data_len as usize {
445                    return Err("Invalid variable data length".to_string());
446                }
447
448                let var_data =
449                    &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
450
451                let type_encoding =
452                    TypeKind::from_u8(data_struct.type_encoding).unwrap_or(TypeKind::Unknown);
453
454                // Use FormatPrinter with type context for enhanced formatting
455                let type_index = data_struct.type_index; // Copy to avoid packed field alignment issues
456                tracing::debug!("streaming_parser - type_index = {}", type_index);
457                tracing::debug!(
458                    "streaming_parser - TraceContext has {} types",
459                    trace_context.types.len()
460                );
461
462                let formatted_value = match trace_context.get_type(type_index) {
463                    Some(type_info) => {
464                        tracing::debug!(
465                            "streaming_parser - Found type_info for index {}",
466                            type_index
467                        );
468                        // Use advanced formatting with full type information
469                        crate::format_printer::FormatPrinter::format_data_with_type_info(
470                            var_data, type_info,
471                        )
472                    }
473                    None => {
474                        tracing::debug!(
475                            "streaming_parser - No type_info found for index {}",
476                            type_index
477                        );
478                        // Type information missing - this indicates a serious compiler bug
479                        format!(
480                            "<COMPILER_ERROR: type_index {type_index} not found in TraceContext>"
481                        )
482                    }
483                };
484
485                ParsedInstruction::PrintVariable {
486                    name: var_name.to_string(),
487                    type_encoding,
488                    formatted_value,
489                    raw_data: var_data.to_vec(),
490                }
491            }
492
493            t if t == InstructionType::ExprError as u8 => {
494                let (data_struct, _) =
495                    crate::trace_event::ExprErrorData::read_from_prefix(inst_data)
496                        .map_err(|_| "Invalid ExprError data".to_string())?;
497                let si = data_struct.string_index;
498                let expr = match trace_context.get_string(si) {
499                    Some(s) => s.to_string(),
500                    None => format!("<INVALID_EXPR_INDEX_{si}>"),
501                };
502                ParsedInstruction::ExprError {
503                    expr,
504                    error_code: data_struct.error_code,
505                    flags: data_struct.flags,
506                    failing_addr: data_struct.failing_addr,
507                }
508            }
509
510            t if t == InstructionType::PrintComplexFormat as u8 => {
511                let (format_data, _) = PrintComplexFormatData::read_from_prefix(inst_data)
512                    .map_err(|_| "Invalid PrintComplexFormat data".to_string())?;
513
514                // Parse complex variable data
515                let mut complex_variables = Vec::new();
516                let mut data_offset = std::mem::size_of::<PrintComplexFormatData>();
517
518                for _ in 0..format_data.arg_count {
519                    if data_offset + PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET > inst_data.len() {
520                        return Err("Invalid PrintComplexFormat argument data".to_string());
521                    }
522
523                    // Read complex variable header: var_name_index, type_index, access_path_len, status
524                    let var_name_index = u16::from_le_bytes([
525                        inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET],
526                        inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET + 1],
527                    ]);
528                    let type_index = u16::from_le_bytes([
529                        inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET],
530                        inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET + 1],
531                    ]);
532                    let access_path_len = inst_data
533                        [data_offset + PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_LEN_OFFSET]
534                        as usize;
535                    let status = inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_STATUS_OFFSET];
536                    data_offset += PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET;
537
538                    // Read access path
539                    if data_offset + access_path_len > inst_data.len() {
540                        return Err("Invalid PrintComplexFormat access path".to_string());
541                    }
542                    let access_path_bytes = &inst_data[data_offset..data_offset + access_path_len];
543                    let access_path = String::from_utf8_lossy(access_path_bytes).to_string();
544                    data_offset += access_path_len;
545
546                    // Read data length
547                    if data_offset + PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE > inst_data.len() {
548                        return Err("Invalid PrintComplexFormat data length".to_string());
549                    }
550                    let data_len =
551                        u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
552                    data_offset += PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE;
553
554                    // Read variable data
555                    if data_offset + data_len as usize > inst_data.len() {
556                        return Err("Invalid PrintComplexFormat variable data".to_string());
557                    }
558                    let var_data = inst_data[data_offset..data_offset + data_len as usize].to_vec();
559                    data_offset += data_len as usize;
560
561                    complex_variables.push(crate::format_printer::ParsedComplexVariable {
562                        var_name_index,
563                        type_index,
564                        access_path,
565                        status,
566                        data: var_data,
567                    });
568                }
569
570                // Use FormatPrinter to generate formatted output
571                let formatted_output =
572                    crate::format_printer::FormatPrinter::format_complex_print_data(
573                        format_data.format_string_index,
574                        &complex_variables,
575                        trace_context,
576                    );
577
578                ParsedInstruction::PrintComplexFormat { formatted_output }
579            }
580
581            t if t == InstructionType::Backtrace as u8 => {
582                let (data_struct, _) = BacktraceData::read_from_prefix(inst_data)
583                    .map_err(|_| "Invalid Backtrace data".to_string())?;
584
585                let requested_depth = data_struct.requested_depth;
586                let frame_count = data_struct.frame_count;
587                let flags = data_struct.flags;
588                let status = BacktraceStatus::from_u8(data_struct.status);
589                let error_code = data_struct.error_code;
590                let frame_offset = BACKTRACE_DATA_SIZE;
591                let available_frames =
592                    inst_data.len().saturating_sub(frame_offset) / BACKTRACE_FRAME_DATA_SIZE;
593                let parsed_frames = frame_count as usize;
594                if frame_count > requested_depth || parsed_frames > available_frames {
595                    return Err("Invalid Backtrace data".to_string());
596                }
597
598                let mut frames = Vec::with_capacity(parsed_frames);
599                for index in 0..parsed_frames {
600                    let start = frame_offset + index * BACKTRACE_FRAME_DATA_SIZE;
601                    let end = start + BACKTRACE_FRAME_DATA_SIZE;
602                    let (frame, _) = BacktraceFrameData::read_from_prefix(&inst_data[start..end])
603                        .map_err(|_| "Invalid Backtrace frame data".to_string())?;
604                    frames.push(ParsedBacktraceFrame {
605                        module_cookie: frame.module_cookie,
606                        pc: frame.pc,
607                        raw_ip: frame.raw_ip,
608                        flags: frame.flags,
609                    });
610                }
611
612                ParsedInstruction::Backtrace {
613                    requested_depth,
614                    flags,
615                    status,
616                    error_code,
617                    frames,
618                }
619            }
620
621            t if t == InstructionType::PrintComplexVariable as u8 => {
622                let (data_struct, _) = PrintComplexVariableData::read_from_prefix(inst_data)
623                    .map_err(|_| "Invalid PrintComplexVariable data".to_string())?;
624
625                // Extract variable name
626                let var_name_index = data_struct.var_name_index;
627                let var_name = trace_context
628                    .get_variable_name(var_name_index)
629                    .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
630
631                // Extract access path
632                let access_path_len = data_struct.access_path_len as usize;
633                let struct_size = std::mem::size_of::<PrintComplexVariableData>();
634
635                if inst_data.len() < struct_size + access_path_len {
636                    return Err("Invalid PrintComplexVariable access path length".to_string());
637                }
638
639                let access_path_bytes = &inst_data[struct_size..struct_size + access_path_len];
640                let access_path = String::from_utf8_lossy(access_path_bytes);
641
642                // Extract variable data (either value or error payload)
643                let var_data_offset = struct_size + access_path_len;
644                if inst_data.len() < var_data_offset + data_struct.data_len as usize {
645                    return Err("Invalid PrintComplexVariable data length".to_string());
646                }
647
648                let var_data =
649                    &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
650
651                // Get type information and format with status-aware printer
652                let formatted_value = FormatPrinter::format_complex_variable_with_status(
653                    var_name_index,
654                    data_struct.type_index,
655                    &access_path,
656                    var_data,
657                    data_struct.status,
658                    trace_context,
659                );
660
661                ParsedInstruction::PrintComplexVariable {
662                    name: var_name.to_string(),
663                    access_path: access_path.to_string(),
664                    type_index: data_struct.type_index,
665                    formatted_value,
666                    raw_data: var_data.to_vec(),
667                }
668            }
669
670            t if t == InstructionType::EndInstruction as u8 => {
671                let (data_struct, _) = EndInstructionData::read_from_prefix(inst_data)
672                    .map_err(|_| "Invalid EndInstruction data".to_string())?;
673
674                ParsedInstruction::EndInstruction {
675                    total_instructions: data_struct.total_instructions,
676                    execution_status: data_struct.execution_status,
677                }
678            }
679
680            _ => {
681                return Err(format!(
682                    "Unknown instruction type: {}",
683                    inst_header.inst_type
684                ))
685            }
686        };
687
688        Ok(Some((instruction, expected_total_size)))
689    }
690
691    /// Reset parser state (useful for error recovery)
692    pub fn reset(&mut self) {
693        self.parse_state = ParseState::WaitingForHeader;
694        self.buffer.clear();
695    }
696
697    /// Get current parse state for debugging
698    pub fn get_state(&self) -> &ParseState {
699        &self.parse_state
700    }
701}
702
703impl ParsedInstruction {
704    /// Return a display string for this instruction
705    pub fn to_display_string(&self) -> String {
706        match self {
707            ParsedInstruction::PrintString { content } => {
708                format!("print \"{content}\"")
709            }
710            ParsedInstruction::PrintVariable {
711                name,
712                type_encoding,
713                formatted_value,
714                raw_data: _,
715            } => {
716                format!("{name} ({type_encoding:?}): {formatted_value}")
717            }
718            ParsedInstruction::ExprError {
719                expr,
720                error_code,
721                flags,
722                failing_addr,
723            } => {
724                // Map code to brief reason aligned with VariableStatus
725                // 1: NullDeref, 2: ReadError, 3: AccessError, 4: Truncated, 5: OffsetsUnavailable, 6: ZeroLength
726                let reason = match *error_code {
727                    1 => "null deref",
728                    2 => "read error",
729                    3 => "access error",
730                    4 => "truncated",
731                    5 => "offsets unavailable",
732                    6 => "zero length",
733                    _ => "error",
734                };
735
736                // Human-friendly flags (best-effort based on expr content)
737                fn readable_flags(expr: &str, flags: u8) -> Option<String> {
738                    if flags == 0 {
739                        return None;
740                    }
741                    let mut tags: Vec<&'static str> = Vec::new();
742                    let is_memcmp = expr.contains("memcmp(");
743                    let is_strncmp = expr.contains("strncmp(") || expr.contains("starts_with(");
744                    if is_memcmp {
745                        if (flags & 0x01) != 0 {
746                            tags.push("first-arg read-fail");
747                        }
748                        if (flags & 0x02) != 0 {
749                            tags.push("second-arg read-fail");
750                        }
751                        if (flags & 0x04) != 0 {
752                            tags.push("len-clamped");
753                        }
754                        if (flags & 0x08) != 0 {
755                            tags.push("len=0");
756                        }
757                    } else if is_strncmp {
758                        if (flags & 0x01) != 0 {
759                            tags.push("read-fail");
760                        }
761                        if (flags & 0x04) != 0 {
762                            tags.push("len-clamped");
763                        }
764                        if (flags & 0x08) != 0 {
765                            tags.push("len=0");
766                        }
767                    } else {
768                        // Unknown producer; fall back to hex for transparency
769                        return Some(format!("0x{flags:02x}"));
770                    }
771                    if tags.is_empty() {
772                        None
773                    } else {
774                        Some(tags.join(","))
775                    }
776                }
777
778                let flags_text = readable_flags(expr, *flags);
779                let addr_text = if *failing_addr != 0 {
780                    format!("at 0x{failing_addr:016x}")
781                } else {
782                    "at NULL".to_string()
783                };
784                let base = format!("ExprError: {expr} ({reason} {addr_text}");
785                match flags_text {
786                    Some(f) => format!("{base}, flags: {f})"),
787                    None => format!("{base})"),
788                }
789            }
790
791            ParsedInstruction::PrintComplexFormat { formatted_output } => formatted_output.clone(),
792            ParsedInstruction::PrintComplexVariable {
793                name: _,
794                access_path: _,
795                type_index: _,
796                formatted_value,
797                raw_data: _,
798            } => {
799                // formatted_value already contains "name = ..." or "name.access = ..."
800                formatted_value.clone()
801            }
802            ParsedInstruction::Backtrace {
803                requested_depth,
804                status,
805                error_code,
806                frames,
807                ..
808            } => {
809                let mut lines = vec![format!(
810                    "backtrace(max_depth={requested_depth}, frames={}, status={})",
811                    frames.len(),
812                    status.label()
813                )];
814                for (index, frame) in frames.iter().enumerate() {
815                    lines.push(format!(
816                        "  #{index} cookie=0x{:016x} pc=0x{:x} raw=0x{:x}",
817                        frame.module_cookie, frame.pc, frame.raw_ip
818                    ));
819                }
820                if *status != BacktraceStatus::Complete {
821                    let suffix = match backtrace_error_label(*error_code) {
822                        Some("unknown") => format!(" (code={error_code})"),
823                        Some(label) => format!(" ({label}, code={error_code})"),
824                        None => String::new(),
825                    };
826                    lines.push(format!("  stopped: {}{}", status.label(), suffix));
827                }
828                lines.join("\n")
829            }
830            ParsedInstruction::EndInstruction {
831                total_instructions,
832                execution_status,
833            } => {
834                let status_str = match *execution_status {
835                    0 => "success",
836                    1 => "partial_failure",
837                    2 => "complete_failure",
838                    _ => "unknown",
839                };
840                format!("end({total_instructions} instructions, {status_str})")
841            }
842        }
843    }
844
845    /// Return the instruction type as a string
846    pub fn instruction_type(&self) -> String {
847        match self {
848            ParsedInstruction::PrintString { .. } => "PrintString".to_string(),
849            ParsedInstruction::PrintVariable { .. } => "PrintVariable".to_string(),
850            ParsedInstruction::ExprError { .. } => "ExprError".to_string(),
851
852            ParsedInstruction::PrintComplexFormat { .. } => "PrintComplexFormat".to_string(),
853            ParsedInstruction::PrintComplexVariable { .. } => "PrintComplexVariable".to_string(),
854            ParsedInstruction::Backtrace { .. } => "Backtrace".to_string(),
855            ParsedInstruction::EndInstruction { .. } => "EndInstruction".to_string(),
856        }
857    }
858}
859
860#[cfg(test)]
861mod tests {
862    use super::*;
863
864    #[test]
865    fn test_streaming_parser() {
866        let mut trace_context = TraceContext::new();
867        let _str_idx = trace_context.add_string("hello world".to_string());
868
869        let mut parser = StreamingTraceParser::new();
870
871        // Create test segments
872        let header = TraceEventHeader {
873            magic: crate::consts::MAGIC,
874        };
875
876        let message = TraceEventMessage {
877            trace_id: 12345,
878            timestamp: 1000,
879            pid: 1001,
880            tid: 2002,
881        };
882
883        // Test header segment (using zerocopy to convert struct to bytes)
884        let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
885        let result = parser
886            .process_segment(header_bytes, &trace_context)
887            .unwrap();
888        assert!(result.is_none()); // Not complete yet
889
890        // Test message segment (using zerocopy to convert struct to bytes)
891        let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
892        let result = parser
893            .process_segment(message_bytes, &trace_context)
894            .unwrap();
895        assert!(result.is_none()); // Not complete yet
896
897        // TODO: Add instruction segments and EndInstruction test
898        // This demonstrates the pattern: TraceContext is managed externally by loader,
899        // not by the parser itself
900    }
901
902    #[test]
903    fn test_parse_exprerror_instruction() {
904        let mut trace_context = TraceContext::new();
905        let expr_idx = trace_context.add_string("memcmp(buf, hex(\"504f\"), 2)".to_string());
906
907        let mut parser = StreamingTraceParser::new();
908
909        // Header
910        let header = TraceEventHeader {
911            magic: crate::consts::MAGIC,
912        };
913        let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
914        assert!(parser
915            .process_segment(header_bytes, &trace_context)
916            .unwrap()
917            .is_none());
918
919        // Message
920        let message = TraceEventMessage {
921            trace_id: 1,
922            timestamp: 0,
923            pid: 123,
924            tid: 456,
925        };
926        let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
927        assert!(parser
928            .process_segment(message_bytes, &trace_context)
929            .unwrap()
930            .is_none());
931
932        // ExprError instruction: header(4) + payload(12)
933        let mut inst = Vec::new();
934        // InstructionHeader
935        inst.push(InstructionType::ExprError as u8); // inst_type
936        inst.extend_from_slice(
937            &(std::mem::size_of::<crate::trace_event::ExprErrorData>() as u16).to_le_bytes(),
938        ); // data_length
939        inst.push(0u8); // reserved
940                        // ExprErrorData payload
941        inst.extend_from_slice(&expr_idx.to_le_bytes()); // string_index
942        inst.push(1u8); // error_code
943        inst.push(0u8); // flags
944        inst.extend_from_slice(&0x1234_5678_9abc_def0u64.to_le_bytes()); // failing_addr
945
946        // EndInstruction
947        inst.push(InstructionType::EndInstruction as u8);
948        inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
949        inst.push(0u8); // reserved
950                        // EndInstructionData
951                        // EndInstructionData: total_instructions:u16, execution_status:u8, reserved:u8
952        inst.extend_from_slice(&1u16.to_le_bytes()); // total_instructions
953        inst.push(1u8); // execution_status
954        inst.push(0u8); // reserved
955
956        let event = parser
957            .process_segment(&inst, &trace_context)
958            .unwrap()
959            .expect("complete event");
960        assert_eq!(event.trace_id, 1);
961        assert_eq!(event.pid, 123);
962        assert_eq!(event.tid, 456);
963        assert_eq!(event.instructions.len(), 2);
964        match &event.instructions[0] {
965            ParsedInstruction::ExprError {
966                expr,
967                error_code,
968                flags,
969                failing_addr,
970            } => {
971                assert_eq!(expr, "memcmp(buf, hex(\"504f\"), 2)");
972                assert_eq!(*error_code, 1);
973                assert_eq!(*flags, 0);
974                assert_eq!(*failing_addr, 0x1234_5678_9abc_def0u64);
975            }
976            other => panic!("unexpected first instruction: {other:?}"),
977        }
978        match &event.instructions[1] {
979            ParsedInstruction::EndInstruction {
980                total_instructions,
981                execution_status,
982            } => {
983                assert_eq!(*total_instructions, 1);
984                assert_eq!(*execution_status, 1); // partial_failure
985            }
986            other => panic!("unexpected last instruction: {other:?}"),
987        }
988    }
989
990    #[test]
991    fn test_parse_backtrace_instruction_with_frames() {
992        let trace_context = TraceContext::new();
993        let mut parser = StreamingTraceParser::new();
994
995        let header = TraceEventHeader {
996            magic: crate::consts::MAGIC,
997        };
998        parser
999            .process_segment(zerocopy::IntoBytes::as_bytes(&header), &trace_context)
1000            .unwrap();
1001
1002        let message = TraceEventMessage {
1003            trace_id: 7,
1004            timestamp: 0,
1005            pid: 100,
1006            tid: 101,
1007        };
1008        parser
1009            .process_segment(zerocopy::IntoBytes::as_bytes(&message), &trace_context)
1010            .unwrap();
1011
1012        let mut inst = Vec::new();
1013        inst.push(InstructionType::Backtrace as u8);
1014        inst.extend_from_slice(
1015            &((BACKTRACE_DATA_SIZE + BACKTRACE_FRAME_DATA_SIZE) as u16).to_le_bytes(),
1016        );
1017        inst.push(0);
1018        inst.push(8); // requested_depth
1019        inst.push(1); // frame_count
1020        inst.push(BACKTRACE_FLAG_INLINE);
1021        inst.push(BacktraceStatus::UnsupportedCfi as u8);
1022        inst.extend_from_slice(&0u16.to_le_bytes());
1023        inst.extend_from_slice(&0u16.to_le_bytes());
1024        inst.extend_from_slice(&0x1122_3344_5566_7788u64.to_le_bytes());
1025        inst.extend_from_slice(&0x1234u64.to_le_bytes());
1026        inst.extend_from_slice(&0x7fff_0000_1234u64.to_le_bytes());
1027        inst.extend_from_slice(&0u16.to_le_bytes());
1028        inst.extend_from_slice(&0u16.to_le_bytes());
1029        inst.extend_from_slice(&0u32.to_le_bytes());
1030
1031        inst.push(InstructionType::EndInstruction as u8);
1032        inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
1033        inst.push(0);
1034        inst.extend_from_slice(&1u16.to_le_bytes());
1035        inst.push(1);
1036        inst.push(0);
1037
1038        let event = parser
1039            .process_segment(&inst, &trace_context)
1040            .unwrap()
1041            .expect("complete event");
1042        match &event.instructions[0] {
1043            ParsedInstruction::Backtrace {
1044                requested_depth,
1045                flags,
1046                status,
1047                frames,
1048                ..
1049            } => {
1050                assert_eq!(*requested_depth, 8);
1051                assert_eq!(*flags, BACKTRACE_FLAG_INLINE);
1052                assert_eq!(*status, BacktraceStatus::UnsupportedCfi);
1053                assert_eq!(frames.len(), 1);
1054                assert_eq!(frames[0].module_cookie, 0x1122_3344_5566_7788);
1055                assert_eq!(frames[0].pc, 0x1234);
1056                assert_eq!(frames[0].raw_ip, 0x7fff_0000_1234);
1057            }
1058            other => panic!("unexpected instruction: {other:?}"),
1059        }
1060    }
1061
1062    #[test]
1063    fn test_format_string_only_consumes_contiguous_variables() {
1064        let event = ParsedTraceEvent {
1065            trace_id: 1,
1066            timestamp: 0,
1067            pid: 10,
1068            tid: 11,
1069            instructions: vec![
1070                ParsedInstruction::PrintString {
1071                    content: "{} {}".to_string(),
1072                },
1073                ParsedInstruction::PrintString {
1074                    content: "literal".to_string(),
1075                },
1076                ParsedInstruction::PrintVariable {
1077                    name: "value".to_string(),
1078                    type_encoding: TypeKind::I32,
1079                    formatted_value: "42".to_string(),
1080                    raw_data: vec![42],
1081                },
1082                ParsedInstruction::EndInstruction {
1083                    total_instructions: 3,
1084                    execution_status: 0,
1085                },
1086            ],
1087        };
1088
1089        assert_eq!(
1090            event.to_formatted_output(),
1091            vec![
1092                "{} {}".to_string(),
1093                "literal".to_string(),
1094                "value (I32): 42".to_string(),
1095            ]
1096        );
1097    }
1098}