1use 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#[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#[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 Disabled = 0x00,
70 ReceivedMessage = 0x82,
72 ExtendedChunkRequest = 0x83,
74 #[num_enum(catch_all)]
75 Unknown(u8),
76}
77
78#[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 pub timestamp: Time,
86}
87
88#[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 pub kind: PdProtocolTraceEventKind,
95 pub timestamp: Time,
97}
98
99#[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 pub state_events: Vec<PdTraceStateEvent>,
106 pub protocol_events: Vec<PdTraceProtocolEvent>,
108}
109
110impl PdTrace {
111 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 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 .chunks_exact(TRACE_RECORD_SIZE)
210 .map(|record| TraceRecord {
211 code: record[0],
212 timestamp: Time::new::<second>(f64::from(u32::from_le_bytes([
213 record[1], record[2], record[3], record[4],
214 ]))),
215 })
216 .collect();
217 Ok((events, end))
218}
219
220fn append_queue(
221 bytes: &mut Vec<u8>,
222 events: impl ExactSizeIterator<Item = TraceRecord>,
223 name: &str,
224) -> Result<(), KMError> {
225 let size = events
226 .len()
227 .checked_mul(TRACE_RECORD_SIZE)
228 .ok_or_else(|| KMError::InvalidPacket(format!("PD trace {name} queue length overflow")))?;
229 if size > MAX_QUEUE_BYTES {
230 return Err(KMError::InvalidPacket(format!(
231 "PD trace {name} queue is too large: {size} bytes, maximum is {MAX_QUEUE_BYTES}"
232 )));
233 }
234
235 bytes.push(size as u8);
236 for event in events {
237 bytes.push(event.code);
238 bytes.extend_from_slice(&(event.timestamp.get::<second>() as u32).to_le_bytes());
239 }
240 Ok(())
241}
242
243#[cfg(feature = "python")]
244#[pyo3::pymethods]
245impl PdTraceStateEvent {
246 #[getter]
247 fn state_code(&self) -> u8 {
248 self.state.into()
249 }
250
251 #[getter]
252 fn state_name(&self) -> String {
253 format!("{:?}", self.state)
254 }
255
256 #[getter]
257 fn timestamp_seconds(&self) -> f64 {
258 self.timestamp.get::<second>()
259 }
260}
261
262#[cfg(feature = "python")]
263#[pyo3::pymethods]
264impl PdTraceProtocolEvent {
265 #[getter]
266 fn code(&self) -> u8 {
267 self.kind.into()
268 }
269
270 #[getter]
271 fn event_name(&self) -> String {
272 format!("{:?}", self.kind)
273 }
274
275 #[getter]
276 fn timestamp_seconds(&self) -> f64 {
277 self.timestamp.get::<second>()
278 }
279}
280
281#[cfg(feature = "python")]
282#[pyo3::pymethods]
283impl PdTrace {
284 #[getter]
285 fn state_events(&self) -> Vec<PdTraceStateEvent> {
286 self.state_events.clone()
287 }
288
289 #[getter]
290 fn protocol_events(&self) -> Vec<PdTraceProtocolEvent> {
291 self.protocol_events.clone()
292 }
293}