Skip to main content

km003c_lib/
pd_trace.rs

1//! USB PD state-machine trace data exposed through attribute `0x0020`.
2
3use num_enum::{FromPrimitive, IntoPrimitive};
4use uom::si::f64::Time;
5use uom::si::time::second;
6
7use crate::error::KMError;
8
9const TRACE_RECORD_SIZE: usize = 5;
10const MAX_QUEUE_BYTES: usize = 200;
11
12/// Type-C state codes embedded in the KM003C V1.9.9 firmware.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15#[repr(u8)]
16pub enum PdTypeCState {
17    Disabled = 0x00,
18    DelayUnattached = 0x01,
19    AttachedResistance = 0x02,
20    TryResistance = 0x03,
21    AttachedDebSource = 0x04,
22    UnattachedDebSource = 0x05,
23    AttachWaitDebSource = 0x06,
24    TryDebSource = 0x07,
25    AttachedSource = 0x08,
26    UnattachedSource = 0x09,
27    AttachWaitSource = 0x0a,
28    TryWaitSource = 0x0b,
29    TrySource = 0x0c,
30    DebugAccessorySource = 0x0d,
31    AttachedCable = 0x0e,
32    IllegalCable = 0x0f,
33    AttachWaitCable = 0x10,
34    AttachWaitMonitorDefective = 0x11,
35    AttachedLightningPlug = 0x12,
36    AttachWaitLightningPlug = 0x13,
37    AttachedDebSink = 0x14,
38    AttachWaitDebSink = 0x15,
39    TryWaitDebSink = 0x16,
40    AttachedSink = 0x17,
41    AttachWaitSink = 0x18,
42    TryWaitSink = 0x19,
43    TrySink = 0x1a,
44    DebugAccessorySink = 0x1b,
45    AttachedMonitor = 0x1c,
46    AttachWaitMonitor = 0x1d,
47    CableCross = 0x1e,
48    CablePlugShortCircuit = 0x1f,
49    ErrorRecovery = 0x20,
50    PoweredAccessory = 0x21,
51    UnsupportedAccessory = 0x22,
52    AudioAccessory = 0x23,
53    AttachWaitAccessory = 0x24,
54    #[num_enum(catch_all)]
55    Unknown(u8),
56}
57
58/// Firmware protocol-engine trace codes confirmed in KM003C V1.9.9.
59///
60/// Most values in this queue are internal protocol-engine states whose names
61/// are not present in the firmware. The two non-state markers below are
62/// emitted directly by the receive path and have independently recoverable
63/// semantics. All other values are preserved losslessly.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
65#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
66#[repr(u8)]
67pub enum PdProtocolTraceEventKind {
68    /// The protocol engine was reset while the Type-C state detached.
69    Disabled = 0x00,
70    /// A received PD message or extended-message chunk was processed.
71    ReceivedMessage = 0x82,
72    /// The receive path queued a request for the next extended-message chunk.
73    ExtendedChunkRequest = 0x83,
74    #[num_enum(catch_all)]
75    Unknown(u8),
76}
77
78/// One Type-C state transition with a one-second-resolution uptime timestamp.
79#[derive(Debug, Clone, Copy, PartialEq)]
80#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
81#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
82pub struct PdTraceStateEvent {
83    pub state: PdTypeCState,
84    /// Device uptime when the transition was recorded.
85    pub timestamp: Time,
86}
87
88/// One raw protocol/message event with a one-second-resolution timestamp.
89#[derive(Debug, Clone, Copy, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
91#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
92pub struct PdTraceProtocolEvent {
93    /// Typed firmware marker, or a losslessly preserved protocol-engine state.
94    pub kind: PdProtocolTraceEventKind,
95    /// Device uptime when the event was recorded.
96    pub timestamp: Time,
97}
98
99/// Two trace queues returned by the KM003C USB PD state machine.
100#[derive(Debug, Clone, PartialEq)]
101#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
102#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
103pub struct PdTrace {
104    /// High-level USB PD state transitions.
105    pub state_events: Vec<PdTraceStateEvent>,
106    /// Protocol and message-processing events.
107    pub protocol_events: Vec<PdTraceProtocolEvent>,
108}
109
110impl PdTrace {
111    /// Parse the two length-prefixed event queues.
112    pub fn from_bytes(bytes: &[u8]) -> Result<Self, KMError> {
113        let (state_records, offset) = parse_queue(bytes, 0, "state")?;
114        let (protocol_records, offset) = parse_queue(bytes, offset, "protocol")?;
115
116        if offset != bytes.len() {
117            return Err(KMError::InvalidPacket(format!(
118                "PD trace has {} trailing bytes",
119                bytes.len() - offset
120            )));
121        }
122
123        Ok(Self {
124            state_events: state_records
125                .into_iter()
126                .map(|record| PdTraceStateEvent {
127                    state: PdTypeCState::from_primitive(record.code),
128                    timestamp: record.timestamp,
129                })
130                .collect(),
131            protocol_events: protocol_records
132                .into_iter()
133                .map(|record| PdTraceProtocolEvent {
134                    kind: PdProtocolTraceEventKind::from_primitive(record.code),
135                    timestamp: record.timestamp,
136                })
137                .collect(),
138        })
139    }
140
141    /// Serialize the trace using the firmware queue layout.
142    pub fn to_bytes(&self) -> Result<Vec<u8>, KMError> {
143        let mut bytes = Vec::new();
144        append_queue(
145            &mut bytes,
146            self.state_events.iter().map(|event| TraceRecord {
147                code: event.state.into(),
148                timestamp: event.timestamp,
149            }),
150            "state",
151        )?;
152        append_queue(
153            &mut bytes,
154            self.protocol_events.iter().map(|event| TraceRecord {
155                code: event.kind.into(),
156                timestamp: event.timestamp,
157            }),
158            "protocol",
159        )?;
160        Ok(bytes)
161    }
162}
163
164pub(crate) fn payload_size(bytes: &[u8]) -> Result<usize, KMError> {
165    let state_size = queue_size(bytes, 0, "state")?;
166    let protocol_offset = 1 + state_size;
167    let protocol_size = queue_size(bytes, protocol_offset, "protocol")?;
168    Ok(protocol_offset + 1 + protocol_size)
169}
170
171fn queue_size(bytes: &[u8], offset: usize, name: &str) -> Result<usize, KMError> {
172    let size = usize::from(*bytes.get(offset).ok_or_else(|| {
173        KMError::InvalidPacket(format!(
174            "PD trace is missing the {name} queue length at offset {offset}"
175        ))
176    })?);
177
178    if size > MAX_QUEUE_BYTES {
179        return Err(KMError::InvalidPacket(format!(
180            "PD trace {name} queue is too large: {size} bytes, maximum is {MAX_QUEUE_BYTES}"
181        )));
182    }
183    if !size.is_multiple_of(TRACE_RECORD_SIZE) {
184        return Err(KMError::InvalidPacket(format!(
185            "PD trace {name} queue length must be a multiple of {TRACE_RECORD_SIZE}, got {size}"
186        )));
187    }
188    if bytes.len().saturating_sub(offset + 1) < size {
189        return Err(KMError::InvalidPacket(format!(
190            "PD trace {name} queue is truncated: expected {size} bytes, got {}",
191            bytes.len().saturating_sub(offset + 1)
192        )));
193    }
194
195    Ok(size)
196}
197
198#[derive(Clone, Copy)]
199struct TraceRecord {
200    code: u8,
201    timestamp: Time,
202}
203
204fn parse_queue(bytes: &[u8], offset: usize, name: &str) -> Result<(Vec<TraceRecord>, usize), KMError> {
205    let size = queue_size(bytes, offset, name)?;
206    let start = offset + 1;
207    let end = start + size;
208    let events = bytes[start..end]
209        .as_chunks::<TRACE_RECORD_SIZE>()
210        .0
211        .iter()
212        .map(|record| TraceRecord {
213            code: record[0],
214            timestamp: Time::new::<second>(f64::from(u32::from_le_bytes([
215                record[1], record[2], record[3], record[4],
216            ]))),
217        })
218        .collect();
219    Ok((events, end))
220}
221
222fn append_queue(
223    bytes: &mut Vec<u8>,
224    events: impl ExactSizeIterator<Item = TraceRecord>,
225    name: &str,
226) -> Result<(), KMError> {
227    let size = events
228        .len()
229        .checked_mul(TRACE_RECORD_SIZE)
230        .ok_or_else(|| KMError::InvalidPacket(format!("PD trace {name} queue length overflow")))?;
231    if size > MAX_QUEUE_BYTES {
232        return Err(KMError::InvalidPacket(format!(
233            "PD trace {name} queue is too large: {size} bytes, maximum is {MAX_QUEUE_BYTES}"
234        )));
235    }
236
237    bytes.push(size as u8);
238    for event in events {
239        bytes.push(event.code);
240        bytes.extend_from_slice(&(event.timestamp.get::<second>() as u32).to_le_bytes());
241    }
242    Ok(())
243}
244
245#[cfg(feature = "python")]
246#[pyo3::pymethods]
247impl PdTraceStateEvent {
248    #[getter]
249    fn state_code(&self) -> u8 {
250        self.state.into()
251    }
252
253    #[getter]
254    fn state_name(&self) -> String {
255        format!("{:?}", self.state)
256    }
257
258    #[getter]
259    fn timestamp_seconds(&self) -> f64 {
260        self.timestamp.get::<second>()
261    }
262}
263
264#[cfg(feature = "python")]
265#[pyo3::pymethods]
266impl PdTraceProtocolEvent {
267    #[getter]
268    fn code(&self) -> u8 {
269        self.kind.into()
270    }
271
272    #[getter]
273    fn event_name(&self) -> String {
274        format!("{:?}", self.kind)
275    }
276
277    #[getter]
278    fn timestamp_seconds(&self) -> f64 {
279        self.timestamp.get::<second>()
280    }
281}
282
283#[cfg(feature = "python")]
284#[pyo3::pymethods]
285impl PdTrace {
286    #[getter]
287    fn state_events(&self) -> Vec<PdTraceStateEvent> {
288        self.state_events.clone()
289    }
290
291    #[getter]
292    fn protocol_events(&self) -> Vec<PdTraceProtocolEvent> {
293        self.protocol_events.clone()
294    }
295}